jarvis-ai-assistant 0.1.124__py3-none-any.whl → 0.1.126__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/__init__.py +1 -1
- jarvis/jarvis_agent/__init__.py +134 -136
- jarvis/jarvis_code_agent/code_agent.py +198 -52
- jarvis/jarvis_code_agent/file_select.py +6 -19
- jarvis/jarvis_code_agent/patch.py +183 -312
- jarvis/jarvis_code_agent/shell_input_handler.py +22 -0
- jarvis/jarvis_codebase/main.py +89 -86
- jarvis/jarvis_dev/main.py +695 -715
- jarvis/jarvis_git_squash/__init__.py +0 -0
- jarvis/jarvis_git_squash/main.py +81 -0
- jarvis/jarvis_lsp/base.py +0 -12
- jarvis/jarvis_lsp/cpp.py +1 -10
- jarvis/jarvis_lsp/go.py +1 -10
- jarvis/jarvis_lsp/python.py +0 -28
- jarvis/jarvis_lsp/registry.py +2 -3
- jarvis/jarvis_lsp/rust.py +1 -10
- jarvis/jarvis_multi_agent/__init__.py +53 -53
- jarvis/jarvis_platform/ai8.py +2 -1
- jarvis/jarvis_platform/base.py +19 -24
- jarvis/jarvis_platform/kimi.py +2 -3
- jarvis/jarvis_platform/ollama.py +3 -1
- jarvis/jarvis_platform/openai.py +1 -1
- jarvis/jarvis_platform/oyi.py +2 -1
- jarvis/jarvis_platform/registry.py +2 -1
- jarvis/jarvis_platform_manager/main.py +4 -6
- jarvis/jarvis_platform_manager/openai_test.py +0 -1
- jarvis/jarvis_rag/main.py +5 -2
- jarvis/jarvis_smart_shell/main.py +9 -4
- jarvis/jarvis_tools/ask_codebase.py +18 -13
- jarvis/jarvis_tools/ask_user.py +5 -4
- jarvis/jarvis_tools/base.py +22 -8
- jarvis/jarvis_tools/chdir.py +8 -9
- jarvis/jarvis_tools/code_review.py +19 -20
- jarvis/jarvis_tools/create_code_agent.py +6 -6
- jarvis/jarvis_tools/create_sub_agent.py +9 -9
- jarvis/jarvis_tools/execute_shell.py +55 -20
- jarvis/jarvis_tools/execute_shell_script.py +7 -7
- jarvis/jarvis_tools/file_operation.py +39 -10
- jarvis/jarvis_tools/git_commiter.py +20 -17
- jarvis/jarvis_tools/lsp_find_definition.py +8 -8
- jarvis/jarvis_tools/lsp_find_references.py +1 -1
- jarvis/jarvis_tools/lsp_get_diagnostics.py +19 -11
- jarvis/jarvis_tools/lsp_get_document_symbols.py +1 -1
- jarvis/jarvis_tools/lsp_prepare_rename.py +8 -8
- jarvis/jarvis_tools/methodology.py +10 -7
- jarvis/jarvis_tools/rag.py +27 -20
- jarvis/jarvis_tools/read_webpage.py +4 -3
- jarvis/jarvis_tools/registry.py +143 -140
- jarvis/jarvis_tools/{search.py → search_web.py} +10 -7
- jarvis/jarvis_tools/select_code_files.py +4 -4
- jarvis/jarvis_tools/tool_generator.py +33 -34
- jarvis/jarvis_utils/__init__.py +19 -982
- jarvis/jarvis_utils/config.py +138 -0
- jarvis/jarvis_utils/embedding.py +201 -0
- jarvis/jarvis_utils/git_utils.py +120 -0
- jarvis/jarvis_utils/globals.py +82 -0
- jarvis/jarvis_utils/input.py +161 -0
- jarvis/jarvis_utils/methodology.py +128 -0
- jarvis/jarvis_utils/output.py +235 -0
- jarvis/jarvis_utils/utils.py +150 -0
- jarvis_ai_assistant-0.1.126.dist-info/METADATA +305 -0
- jarvis_ai_assistant-0.1.126.dist-info/RECORD +74 -0
- {jarvis_ai_assistant-0.1.124.dist-info → jarvis_ai_assistant-0.1.126.dist-info}/WHEEL +1 -1
- {jarvis_ai_assistant-0.1.124.dist-info → jarvis_ai_assistant-0.1.126.dist-info}/entry_points.txt +1 -0
- jarvis/jarvis_tools/lsp_validate_edit.py +0 -141
- jarvis/jarvis_tools/read_code.py +0 -191
- jarvis_ai_assistant-0.1.124.dist-info/METADATA +0 -460
- jarvis_ai_assistant-0.1.124.dist-info/RECORD +0 -65
- {jarvis_ai_assistant-0.1.124.dist-info → jarvis_ai_assistant-0.1.126.dist-info}/LICENSE +0 -0
- {jarvis_ai_assistant-0.1.124.dist-info → jarvis_ai_assistant-0.1.126.dist-info}/top_level.txt +0 -0
|
@@ -1,22 +1,24 @@
|
|
|
1
1
|
from typing import Dict, Any
|
|
2
|
-
from jarvis.jarvis_utils import OutputType, PrettyOutput, dont_use_local_model, find_git_root
|
|
3
2
|
from jarvis.jarvis_codebase.main import CodeBase
|
|
3
|
+
from jarvis.jarvis_utils.config import dont_use_local_model
|
|
4
|
+
from jarvis.jarvis_utils.git_utils import find_git_root
|
|
5
|
+
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
|
4
6
|
|
|
5
7
|
class AskCodebaseTool:
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
+
"""用于智能代码库查询和分析的工具"""
|
|
9
|
+
|
|
8
10
|
name = "ask_codebase"
|
|
9
|
-
description = "
|
|
11
|
+
description = "查询代码库问题并获取详细分析"
|
|
10
12
|
parameters = {
|
|
11
13
|
"type": "object",
|
|
12
14
|
"properties": {
|
|
13
15
|
"question": {
|
|
14
16
|
"type": "string",
|
|
15
|
-
"description": "
|
|
17
|
+
"description": "关于代码库的问题"
|
|
16
18
|
},
|
|
17
19
|
"top_k": {
|
|
18
20
|
"type": "integer",
|
|
19
|
-
"description": "
|
|
21
|
+
"description": "要分析的最相关文件数量(可选)",
|
|
20
22
|
"default": 20
|
|
21
23
|
}
|
|
22
24
|
},
|
|
@@ -52,14 +54,20 @@ class AskCodebaseTool:
|
|
|
52
54
|
codebase = CodeBase(git_root)
|
|
53
55
|
|
|
54
56
|
# Use ask_codebase method
|
|
55
|
-
|
|
56
|
-
|
|
57
|
+
files, response = codebase.ask_codebase(question, top_k)
|
|
58
|
+
|
|
59
|
+
# Print found files
|
|
60
|
+
if files:
|
|
61
|
+
output = "找到的相关文件:\n"
|
|
62
|
+
for file in files:
|
|
63
|
+
output += f"- {file['file']} ({file['reason']})\n"
|
|
64
|
+
PrettyOutput.print(output, OutputType.INFO, lang="markdown")
|
|
65
|
+
|
|
57
66
|
return {
|
|
58
67
|
"success": True,
|
|
59
68
|
"stdout": response,
|
|
60
69
|
"stderr": ""
|
|
61
70
|
}
|
|
62
|
-
|
|
63
71
|
except Exception as e:
|
|
64
72
|
error_msg = f"分析代码库失败: {str(e)}"
|
|
65
73
|
PrettyOutput.print(error_msg, OutputType.WARNING)
|
|
@@ -68,8 +76,6 @@ class AskCodebaseTool:
|
|
|
68
76
|
"stdout": "",
|
|
69
77
|
"stderr": error_msg
|
|
70
78
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
79
|
def main():
|
|
74
80
|
"""Command line interface for the tool"""
|
|
75
81
|
import argparse
|
|
@@ -79,7 +85,6 @@ def main():
|
|
|
79
85
|
parser.add_argument('--top-k', type=int, help='Number of files to analyze', default=20)
|
|
80
86
|
|
|
81
87
|
args = parser.parse_args()
|
|
82
|
-
|
|
83
88
|
tool = AskCodebaseTool()
|
|
84
89
|
result = tool.execute({
|
|
85
90
|
"question": args.question,
|
|
@@ -93,4 +98,4 @@ def main():
|
|
|
93
98
|
|
|
94
99
|
|
|
95
100
|
if __name__ == "__main__":
|
|
96
|
-
main()
|
|
101
|
+
main()
|
jarvis/jarvis_tools/ask_user.py
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
from typing import Dict, Any
|
|
2
|
-
|
|
3
|
-
from jarvis.jarvis_utils import get_multiline_input
|
|
2
|
+
|
|
3
|
+
from jarvis.jarvis_utils.input import get_multiline_input
|
|
4
|
+
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
|
4
5
|
|
|
5
6
|
class AskUserTool:
|
|
6
7
|
name="ask_user"
|
|
7
|
-
description="""
|
|
8
|
+
description="""当完成任务所需的信息缺失或关键决策信息不足时,向用户提问。用户可以输入多行文本,以空行结束。使用场景:1. 需要用户提供更多信息以完成任务;2. 需要用户做出关键决策;3. 需要用户确认重要操作;4. 需要用户提供额外信息"""
|
|
8
9
|
parameters={
|
|
9
10
|
"type": "object",
|
|
10
11
|
"properties": {
|
|
11
12
|
"question": {
|
|
12
13
|
"type": "string",
|
|
13
|
-
"description": "
|
|
14
|
+
"description": "要向用户提出的问题"
|
|
14
15
|
}
|
|
15
16
|
},
|
|
16
17
|
"required": ["question"]
|
jarvis/jarvis_tools/base.py
CHANGED
|
@@ -1,23 +1,37 @@
|
|
|
1
1
|
from typing import Dict, Any, Callable
|
|
2
2
|
import json
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
3
|
class Tool:
|
|
4
|
+
"""工具类,用于封装工具的基本信息和执行方法"""
|
|
5
|
+
|
|
7
6
|
def __init__(self, name: str, description: str, parameters: Dict, func: Callable):
|
|
7
|
+
"""
|
|
8
|
+
初始化工具对象
|
|
9
|
+
|
|
10
|
+
参数:
|
|
11
|
+
name (str): 工具名称
|
|
12
|
+
description (str): 工具描述
|
|
13
|
+
parameters (Dict): 工具参数定义
|
|
14
|
+
func (Callable): 工具执行函数
|
|
15
|
+
"""
|
|
8
16
|
self.name = name
|
|
9
17
|
self.description = description
|
|
10
18
|
self.parameters = parameters
|
|
11
19
|
self.func = func
|
|
12
|
-
|
|
13
20
|
def to_dict(self) -> Dict:
|
|
14
|
-
"""
|
|
21
|
+
"""将工具对象转换为字典格式,主要用于序列化"""
|
|
15
22
|
return {
|
|
16
23
|
"name": self.name,
|
|
17
24
|
"description": self.description,
|
|
18
25
|
"parameters": json.dumps(self.parameters, ensure_ascii=False)
|
|
19
26
|
}
|
|
20
|
-
|
|
21
27
|
def execute(self, arguments: Dict) -> Dict[str, Any]:
|
|
22
|
-
"""
|
|
23
|
-
|
|
28
|
+
"""
|
|
29
|
+
执行工具函数
|
|
30
|
+
|
|
31
|
+
参数:
|
|
32
|
+
arguments (Dict): 工具执行所需的参数
|
|
33
|
+
|
|
34
|
+
返回:
|
|
35
|
+
Dict[str, Any]: 工具执行结果
|
|
36
|
+
"""
|
|
37
|
+
return self.func(arguments)
|
jarvis/jarvis_tools/chdir.py
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
from typing import Dict, Any
|
|
2
2
|
import os
|
|
3
|
-
from jarvis.jarvis_utils import PrettyOutput, OutputType
|
|
4
3
|
|
|
5
4
|
class ChdirTool:
|
|
6
5
|
name = "chdir"
|
|
7
|
-
description = "
|
|
6
|
+
description = "更改当前工作目录"
|
|
8
7
|
parameters = {
|
|
9
8
|
"type": "object",
|
|
10
9
|
"properties": {
|
|
11
10
|
"path": {
|
|
12
11
|
"type": "string",
|
|
13
|
-
"description": "
|
|
12
|
+
"description": "要切换到的目录路径,支持相对路径和绝对路径"
|
|
14
13
|
}
|
|
15
14
|
},
|
|
16
15
|
"required": ["path"]
|
|
@@ -46,7 +45,7 @@ class ChdirTool:
|
|
|
46
45
|
return {
|
|
47
46
|
"success": False,
|
|
48
47
|
"stdout": "",
|
|
49
|
-
"stderr": f"
|
|
48
|
+
"stderr": f"目录不存在: {path}"
|
|
50
49
|
}
|
|
51
50
|
|
|
52
51
|
# Ensure the path points to a directory, not a file
|
|
@@ -54,7 +53,7 @@ class ChdirTool:
|
|
|
54
53
|
return {
|
|
55
54
|
"success": False,
|
|
56
55
|
"stdout": "",
|
|
57
|
-
"stderr": f"
|
|
56
|
+
"stderr": f"路径不是目录: {path}"
|
|
58
57
|
}
|
|
59
58
|
|
|
60
59
|
# Capture current directory and attempt to change to new path
|
|
@@ -63,7 +62,7 @@ class ChdirTool:
|
|
|
63
62
|
|
|
64
63
|
return {
|
|
65
64
|
"success": True,
|
|
66
|
-
"stdout": f"
|
|
65
|
+
"stdout": f"成功切换工作目录:\n原目录: {old_path}\n新目录: {path}",
|
|
67
66
|
"stderr": ""
|
|
68
67
|
}
|
|
69
68
|
|
|
@@ -72,12 +71,12 @@ class ChdirTool:
|
|
|
72
71
|
return {
|
|
73
72
|
"success": False,
|
|
74
73
|
"stdout": "",
|
|
75
|
-
"stderr": f"
|
|
74
|
+
"stderr": f"无权限访问目录: {path}"
|
|
76
75
|
}
|
|
77
76
|
# Catch-all for any other unexpected errors during directory change
|
|
78
77
|
except Exception as e:
|
|
79
78
|
return {
|
|
80
79
|
"success": False,
|
|
81
80
|
"stdout": "",
|
|
82
|
-
"stderr": f"
|
|
83
|
-
}
|
|
81
|
+
"stderr": f"切换目录失败: {str(e)}"
|
|
82
|
+
}
|
|
@@ -1,44 +1,41 @@
|
|
|
1
|
-
from typing import Dict, Any
|
|
1
|
+
from typing import Dict, Any
|
|
2
2
|
import subprocess
|
|
3
|
-
import yaml
|
|
4
3
|
from jarvis.jarvis_platform.registry import PlatformRegistry
|
|
5
4
|
from jarvis.jarvis_tools.registry import ToolRegistry
|
|
6
|
-
from jarvis.jarvis_utils import OutputType, PrettyOutput, init_env, find_git_root
|
|
7
5
|
from jarvis.jarvis_agent import Agent
|
|
8
6
|
import re
|
|
9
7
|
|
|
8
|
+
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
|
9
|
+
from jarvis.jarvis_utils.utils import init_env
|
|
10
|
+
|
|
10
11
|
class CodeReviewTool:
|
|
11
12
|
name = "code_review"
|
|
12
|
-
description = "
|
|
13
|
+
description = "自动代码审查工具,用于分析代码变更"
|
|
13
14
|
parameters = {
|
|
14
15
|
"type": "object",
|
|
15
16
|
"properties": {
|
|
16
17
|
"review_type": {
|
|
17
18
|
"type": "string",
|
|
18
|
-
"description": "
|
|
19
|
+
"description": "审查类型:'commit' 审查特定提交,'current' 审查当前变更,'range' 审查提交范围",
|
|
19
20
|
"enum": ["commit", "current", "range"],
|
|
20
21
|
"default": "current"
|
|
21
22
|
},
|
|
22
23
|
"commit_sha": {
|
|
23
24
|
"type": "string",
|
|
24
|
-
"description": "
|
|
25
|
+
"description": "要分析的提交SHA(review_type='commit'时必填)"
|
|
25
26
|
},
|
|
26
27
|
"start_commit": {
|
|
27
28
|
"type": "string",
|
|
28
|
-
"description": "
|
|
29
|
+
"description": "起始提交SHA(review_type='range'时必填)"
|
|
29
30
|
},
|
|
30
31
|
"end_commit": {
|
|
31
32
|
"type": "string",
|
|
32
|
-
"description": "
|
|
33
|
+
"description": "结束提交SHA(review_type='range'时必填)"
|
|
33
34
|
}
|
|
34
35
|
},
|
|
35
36
|
"required": []
|
|
36
37
|
}
|
|
37
38
|
|
|
38
|
-
def __init__(self):
|
|
39
|
-
init_env()
|
|
40
|
-
self.repo_root = find_git_root()
|
|
41
|
-
|
|
42
39
|
def execute(self, args: Dict[str, Any]) -> Dict[str, Any]:
|
|
43
40
|
try:
|
|
44
41
|
review_type = args.get("review_type", "current").strip()
|
|
@@ -170,13 +167,13 @@ PROPOSED DEFENSE:
|
|
|
170
167
|
agent = Agent(
|
|
171
168
|
system_prompt=system_prompt,
|
|
172
169
|
name="Code Review Agent",
|
|
173
|
-
summary_prompt="""Please generate a concise summary report of the code review, format as
|
|
170
|
+
summary_prompt="""Please generate a concise summary report of the code review in Chinese, format as follows:
|
|
174
171
|
<REPORT>
|
|
175
|
-
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
172
|
+
- 文件: xxxx.py
|
|
173
|
+
位置: [起始行号, 结束行号]
|
|
174
|
+
描述: # 仅描述在差异中直接观察到的问题
|
|
175
|
+
严重程度: # 根据具体证据分为严重/重要/次要
|
|
176
|
+
建议: # 针对观察到的代码的具体改进建议
|
|
180
177
|
</REPORT>""",
|
|
181
178
|
is_sub_agent=True,
|
|
182
179
|
output_handler=[tool_registry],
|
|
@@ -207,6 +204,8 @@ def extract_code_report(result: str) -> str:
|
|
|
207
204
|
def main():
|
|
208
205
|
"""CLI entry point"""
|
|
209
206
|
import argparse
|
|
207
|
+
|
|
208
|
+
init_env()
|
|
210
209
|
|
|
211
210
|
parser = argparse.ArgumentParser(description='Autonomous code review tool')
|
|
212
211
|
parser.add_argument('--type', choices=['commit', 'current', 'range'], default='current',
|
|
@@ -236,7 +235,7 @@ def main():
|
|
|
236
235
|
result = tool.execute(tool_args)
|
|
237
236
|
|
|
238
237
|
if result["success"]:
|
|
239
|
-
PrettyOutput.section("
|
|
238
|
+
PrettyOutput.section("自动代码审查结果:", OutputType.SUCCESS)
|
|
240
239
|
report = extract_code_report(result["stdout"])
|
|
241
240
|
PrettyOutput.print(report, OutputType.SUCCESS, lang="yaml")
|
|
242
241
|
|
|
@@ -244,4 +243,4 @@ def main():
|
|
|
244
243
|
PrettyOutput.print(result["stderr"], OutputType.WARNING)
|
|
245
244
|
|
|
246
245
|
if __name__ == "__main__":
|
|
247
|
-
main()
|
|
246
|
+
main()
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import os
|
|
2
1
|
from typing import Dict, Any
|
|
3
2
|
from jarvis.jarvis_code_agent.code_agent import CodeAgent
|
|
4
3
|
from jarvis.jarvis_tools.git_commiter import GitCommitTool
|
|
5
4
|
from jarvis.jarvis_tools.code_review import CodeReviewTool, extract_code_report
|
|
6
|
-
from jarvis.jarvis_utils import
|
|
5
|
+
from jarvis.jarvis_utils.git_utils import get_latest_commit_hash, has_uncommitted_changes
|
|
6
|
+
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
|
7
7
|
|
|
8
8
|
class CreateCodeAgentTool:
|
|
9
|
-
"""
|
|
9
|
+
"""用于管理代码开发工作流的工具"""
|
|
10
10
|
|
|
11
11
|
name = "create_code_agent"
|
|
12
|
-
description = "
|
|
12
|
+
description = "技术代码实现和开发过程管理工具"
|
|
13
13
|
parameters = {
|
|
14
|
-
"requirement": "
|
|
14
|
+
"requirement": "代码实现的技术规范"
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
|
|
@@ -109,4 +109,4 @@ def main():
|
|
|
109
109
|
PrettyOutput.print(result["stderr"], OutputType.WARNING)
|
|
110
110
|
|
|
111
111
|
if __name__ == "__main__":
|
|
112
|
-
main()
|
|
112
|
+
main()
|
|
@@ -2,38 +2,38 @@ from typing import Dict, Any
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
from jarvis.jarvis_agent import Agent, origin_agent_system_prompt
|
|
5
|
-
from jarvis.
|
|
6
|
-
|
|
5
|
+
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
|
6
|
+
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class SubAgentTool:
|
|
10
10
|
name = "create_sub_agent"
|
|
11
|
-
description = "
|
|
11
|
+
description = "创建子代理以处理特定任务,子代理将生成任务总结报告"
|
|
12
12
|
parameters = {
|
|
13
13
|
"type": "object",
|
|
14
14
|
"properties": {
|
|
15
15
|
"agent_name": {
|
|
16
16
|
"type": "string",
|
|
17
|
-
"description": "
|
|
17
|
+
"description": "子代理名称"
|
|
18
18
|
},
|
|
19
19
|
"task": {
|
|
20
20
|
"type": "string",
|
|
21
|
-
"description": "
|
|
21
|
+
"description": "要完成的特定任务"
|
|
22
22
|
},
|
|
23
23
|
"context": {
|
|
24
24
|
"type": "string",
|
|
25
|
-
"description": "
|
|
25
|
+
"description": "与任务相关的上下文信息",
|
|
26
26
|
"default": ""
|
|
27
27
|
},
|
|
28
28
|
"goal": {
|
|
29
29
|
"type": "string",
|
|
30
|
-
"description": "
|
|
30
|
+
"description": "任务的完成目标",
|
|
31
31
|
"default": ""
|
|
32
32
|
},
|
|
33
33
|
"files": {
|
|
34
34
|
"type": "array",
|
|
35
35
|
"items": {"type": "string"},
|
|
36
|
-
"description": "
|
|
36
|
+
"description": "相关文件路径列表,用于文件问答和处理",
|
|
37
37
|
"default": []
|
|
38
38
|
}
|
|
39
39
|
},
|
|
@@ -83,4 +83,4 @@ class SubAgentTool:
|
|
|
83
83
|
"success": False,
|
|
84
84
|
"stdout": "",
|
|
85
85
|
"stderr": f"Sub-agent execution failed: {str(e)}"
|
|
86
|
-
}
|
|
86
|
+
}
|
|
@@ -1,55 +1,89 @@
|
|
|
1
|
+
# Shell command execution module
|
|
2
|
+
#
|
|
3
|
+
# Provides functionality to execute shell commands safely with:
|
|
4
|
+
# - Command escaping
|
|
5
|
+
# - Output capturing
|
|
6
|
+
# - Temporary file management
|
|
7
|
+
# - Error handling
|
|
1
8
|
from typing import Dict, Any
|
|
2
9
|
import os
|
|
3
10
|
import tempfile
|
|
4
11
|
from pathlib import Path
|
|
5
|
-
|
|
6
|
-
from jarvis.jarvis_utils import OutputType, PrettyOutput
|
|
7
|
-
|
|
8
|
-
|
|
12
|
+
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
|
9
13
|
class ShellTool:
|
|
14
|
+
"""Shell command execution tool
|
|
15
|
+
|
|
16
|
+
Attributes:
|
|
17
|
+
name: Tool identifier used in API
|
|
18
|
+
description: Tool description for API documentation
|
|
19
|
+
parameters: JSON schema for command parameters
|
|
20
|
+
"""
|
|
10
21
|
name = "execute_shell"
|
|
11
|
-
description = ""
|
|
12
|
-
|
|
22
|
+
description = "执行Shell命令并返回结果"
|
|
13
23
|
parameters = {
|
|
14
24
|
"type": "object",
|
|
15
25
|
"properties": {
|
|
16
26
|
"command": {
|
|
17
27
|
"type": "string",
|
|
18
|
-
"description": "Shell
|
|
28
|
+
"description": "要执行的Shell命令"
|
|
19
29
|
}
|
|
20
30
|
},
|
|
21
31
|
"required": ["command"]
|
|
22
32
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
33
|
def _escape_command(self, cmd: str) -> str:
|
|
26
|
-
"""Escape special characters in command
|
|
34
|
+
"""Escape special characters in command to prevent shell injection
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
cmd: Raw command string
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
Escaped command string with single quotes properly handled
|
|
41
|
+
"""
|
|
27
42
|
return cmd.replace("'", "'\"'\"'")
|
|
28
|
-
|
|
29
43
|
def execute(self, args: Dict) -> Dict[str, Any]:
|
|
30
|
-
"""Execute shell command
|
|
44
|
+
"""Execute shell command and capture output
|
|
45
|
+
|
|
46
|
+
Steps:
|
|
47
|
+
1. Validate and clean input command
|
|
48
|
+
2. Create temporary file for output capture
|
|
49
|
+
3. Execute command with output redirection
|
|
50
|
+
4. Read and process output file
|
|
51
|
+
5. Clean up temporary resources
|
|
52
|
+
6. Return structured results
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
args: Dictionary containing 'command' parameter
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
Dictionary with:
|
|
59
|
+
- success: Boolean indicating command execution status
|
|
60
|
+
- stdout: Command output
|
|
61
|
+
- stderr: Error message if execution failed
|
|
62
|
+
"""
|
|
31
63
|
try:
|
|
64
|
+
# Get and clean command input
|
|
32
65
|
command = args["command"].strip()
|
|
33
66
|
|
|
34
|
-
# Generate temporary file name
|
|
67
|
+
# Generate temporary file name using process ID for uniqueness
|
|
35
68
|
output_file = os.path.join(tempfile.gettempdir(), f"jarvis_shell_{os.getpid()}.log")
|
|
36
69
|
|
|
37
|
-
# Escape special characters in command
|
|
70
|
+
# Escape special characters in command to prevent injection
|
|
38
71
|
escaped_command = self._escape_command(command)
|
|
39
72
|
|
|
40
|
-
#
|
|
73
|
+
# Use script command to capture both stdout and stderr
|
|
41
74
|
tee_command = f"script -q -c '{escaped_command}' {output_file}"
|
|
42
75
|
|
|
76
|
+
# Log command execution
|
|
43
77
|
PrettyOutput.print(f"执行命令: {command}", OutputType.INFO)
|
|
44
78
|
|
|
45
|
-
# Execute command
|
|
79
|
+
# Execute command and capture return code
|
|
46
80
|
return_code = os.system(tee_command)
|
|
47
81
|
|
|
48
|
-
# Read output file
|
|
82
|
+
# Read and process output file
|
|
49
83
|
try:
|
|
50
84
|
with open(output_file, 'r', encoding='utf-8', errors='replace') as f:
|
|
51
85
|
output = f.read()
|
|
52
|
-
# Remove header and footer added by script
|
|
86
|
+
# Remove header and footer added by script command
|
|
53
87
|
if output:
|
|
54
88
|
lines = output.splitlines()
|
|
55
89
|
if len(lines) > 2:
|
|
@@ -60,6 +94,7 @@ class ShellTool:
|
|
|
60
94
|
# Clean up temporary file
|
|
61
95
|
Path(output_file).unlink(missing_ok=True)
|
|
62
96
|
|
|
97
|
+
# Return successful result
|
|
63
98
|
return {
|
|
64
99
|
"success": True,
|
|
65
100
|
"stdout": output,
|
|
@@ -67,7 +102,7 @@ class ShellTool:
|
|
|
67
102
|
}
|
|
68
103
|
|
|
69
104
|
except Exception as e:
|
|
70
|
-
# Ensure temporary file is cleaned up
|
|
105
|
+
# Ensure temporary file is cleaned up even if error occurs
|
|
71
106
|
if 'output_file' in locals():
|
|
72
107
|
Path(output_file).unlink(missing_ok=True)
|
|
73
108
|
PrettyOutput.print(str(e), OutputType.ERROR)
|
|
@@ -75,4 +110,4 @@ class ShellTool:
|
|
|
75
110
|
"success": False,
|
|
76
111
|
"stdout": "",
|
|
77
112
|
"stderr": str(e)
|
|
78
|
-
}
|
|
113
|
+
}
|
|
@@ -2,17 +2,19 @@ from typing import Dict, Any
|
|
|
2
2
|
import os
|
|
3
3
|
import tempfile
|
|
4
4
|
from pathlib import Path
|
|
5
|
-
|
|
5
|
+
|
|
6
|
+
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
|
7
|
+
|
|
6
8
|
|
|
7
9
|
class ShellScriptTool:
|
|
8
10
|
name = "execute_shell_script"
|
|
9
|
-
description = ""
|
|
11
|
+
description = "执行Shell脚本文件并返回结果"
|
|
10
12
|
parameters = {
|
|
11
13
|
"type": "object",
|
|
12
14
|
"properties": {
|
|
13
15
|
"script_content": {
|
|
14
16
|
"type": "string",
|
|
15
|
-
"description": "
|
|
17
|
+
"description": "要执行的Shell脚本内容"
|
|
16
18
|
}
|
|
17
19
|
},
|
|
18
20
|
"required": ["script_content"]
|
|
@@ -33,12 +35,10 @@ class ShellScriptTool:
|
|
|
33
35
|
try:
|
|
34
36
|
with open(script_path, 'w', encoding='utf-8') as f:
|
|
35
37
|
f.write(script_content)
|
|
36
|
-
os.chmod(script_path, 0o755) # Make script executable
|
|
37
|
-
|
|
38
38
|
# Use execute_shell to run the script
|
|
39
39
|
from jarvis.jarvis_tools.execute_shell import ShellTool
|
|
40
40
|
shell_tool = ShellTool()
|
|
41
|
-
result = shell_tool.execute({"command": script_path})
|
|
41
|
+
result = shell_tool.execute({"command": f"bash {script_path}"})
|
|
42
42
|
|
|
43
43
|
return {
|
|
44
44
|
"success": result["success"],
|
|
@@ -55,4 +55,4 @@ class ShellScriptTool:
|
|
|
55
55
|
"success": False,
|
|
56
56
|
"stdout": "",
|
|
57
57
|
"stderr": str(e)
|
|
58
|
-
}
|
|
58
|
+
}
|
|
@@ -1,19 +1,20 @@
|
|
|
1
|
-
from typing import Dict, Any
|
|
1
|
+
from typing import Dict, Any
|
|
2
2
|
import os
|
|
3
3
|
|
|
4
|
-
from jarvis.jarvis_utils import OutputType, PrettyOutput
|
|
4
|
+
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
|
5
|
+
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
class FileOperationTool:
|
|
8
9
|
name = "file_operation"
|
|
9
|
-
description = "
|
|
10
|
+
description = "用于读写多个文件的操作工具"
|
|
10
11
|
parameters = {
|
|
11
12
|
"type": "object",
|
|
12
13
|
"properties": {
|
|
13
14
|
"operation": {
|
|
14
15
|
"type": "string",
|
|
15
16
|
"enum": ["read", "write"],
|
|
16
|
-
"description": "
|
|
17
|
+
"description": "要执行的文件操作类型(读取或写入多个文件)"
|
|
17
18
|
},
|
|
18
19
|
"files": {
|
|
19
20
|
"type": "array",
|
|
@@ -25,13 +26,14 @@ class FileOperationTool:
|
|
|
25
26
|
},
|
|
26
27
|
"required": ["path"]
|
|
27
28
|
},
|
|
28
|
-
"description": "
|
|
29
|
+
"description": "要操作的文件列表"
|
|
29
30
|
}
|
|
30
31
|
},
|
|
31
32
|
"required": ["operation", "files"]
|
|
32
33
|
}
|
|
33
34
|
|
|
34
|
-
def _handle_single_file(self, operation: str, filepath: str, content: str = ""
|
|
35
|
+
def _handle_single_file(self, operation: str, filepath: str, content: str = "",
|
|
36
|
+
start_line: int = 1, end_line: int = -1) -> Dict[str, Any]:
|
|
35
37
|
"""Handle operations for a single file"""
|
|
36
38
|
try:
|
|
37
39
|
abs_path = os.path.abspath(filepath)
|
|
@@ -54,8 +56,29 @@ class FileOperationTool:
|
|
|
54
56
|
"stderr": "File too large (>10MB)"
|
|
55
57
|
}
|
|
56
58
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
+
with open(abs_path, 'r', encoding='utf-8') as f:
|
|
60
|
+
lines = f.readlines()
|
|
61
|
+
|
|
62
|
+
# Handle line range
|
|
63
|
+
total_lines = len(lines)
|
|
64
|
+
start_line = start_line if start_line >= 0 else total_lines + start_line + 1
|
|
65
|
+
end_line = end_line if end_line >= 0 else total_lines + end_line + 1
|
|
66
|
+
start_line = max(1, min(start_line, total_lines))
|
|
67
|
+
end_line = max(1, min(end_line, total_lines))
|
|
68
|
+
if end_line == -1:
|
|
69
|
+
end_line = total_lines
|
|
70
|
+
|
|
71
|
+
if start_line > end_line:
|
|
72
|
+
error_msg = f"无效的行范围 [{start_line, end_line}] (文件总行数: {total_lines})"
|
|
73
|
+
PrettyOutput.print(error_msg, OutputType.WARNING)
|
|
74
|
+
return {
|
|
75
|
+
"success": False,
|
|
76
|
+
"stdout": "",
|
|
77
|
+
"stderr": error_msg
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
content = "".join(lines[start_line - 1:end_line])
|
|
81
|
+
output = f"\文件: {abs_path}\行: [{start_line}-{end_line}]\n{content}" + "\n\n" + "="*80 + "\n\n"
|
|
59
82
|
|
|
60
83
|
return {
|
|
61
84
|
"success": True,
|
|
@@ -119,7 +142,13 @@ class FileOperationTool:
|
|
|
119
142
|
continue
|
|
120
143
|
|
|
121
144
|
content = file_info.get("content", "") if operation == "write" else ""
|
|
122
|
-
result = self._handle_single_file(
|
|
145
|
+
result = self._handle_single_file(
|
|
146
|
+
operation,
|
|
147
|
+
file_info["path"].strip(),
|
|
148
|
+
content,
|
|
149
|
+
file_info.get("start_line", 1),
|
|
150
|
+
file_info.get("end_line", -1)
|
|
151
|
+
)
|
|
123
152
|
|
|
124
153
|
if result["success"]:
|
|
125
154
|
all_outputs.append(result["stdout"])
|
|
@@ -142,4 +171,4 @@ class FileOperationTool:
|
|
|
142
171
|
"success": False,
|
|
143
172
|
"stdout": "",
|
|
144
173
|
"stderr": f"File operation failed: {str(e)}"
|
|
145
|
-
}
|
|
174
|
+
}
|