jarvis-ai-assistant 0.1.176__py3-none-any.whl → 0.1.178__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 +33 -62
- jarvis/jarvis_agent/jarvis.py +10 -10
- jarvis/jarvis_agent/main.py +6 -6
- jarvis/jarvis_code_agent/code_agent.py +10 -6
- jarvis/jarvis_code_analysis/code_review.py +1 -1
- jarvis/jarvis_dev/main.py +1 -1
- jarvis/jarvis_event/__init__.py +0 -0
- jarvis/jarvis_git_details/main.py +1 -1
- jarvis/jarvis_git_squash/main.py +1 -1
- jarvis/jarvis_git_utils/git_commiter.py +1 -1
- jarvis/jarvis_multi_agent/main.py +1 -1
- jarvis/jarvis_platform/base.py +6 -3
- jarvis/jarvis_platform/openai.py +66 -10
- jarvis/jarvis_platform/yuanbao.py +1 -1
- jarvis/jarvis_platform_manager/main.py +1 -1
- jarvis/jarvis_smart_shell/main.py +4 -2
- jarvis/jarvis_tools/ask_codebase.py +3 -2
- jarvis/jarvis_tools/cli/main.py +28 -1
- jarvis/jarvis_tools/edit_file.py +186 -118
- jarvis/jarvis_tools/read_code.py +0 -2
- jarvis/jarvis_tools/registry.py +31 -1
- jarvis/jarvis_utils/config.py +13 -4
- jarvis/jarvis_utils/output.py +36 -7
- jarvis/jarvis_utils/utils.py +19 -1
- {jarvis_ai_assistant-0.1.176.dist-info → jarvis_ai_assistant-0.1.178.dist-info}/METADATA +4 -4
- {jarvis_ai_assistant-0.1.176.dist-info → jarvis_ai_assistant-0.1.178.dist-info}/RECORD +31 -37
- {jarvis_ai_assistant-0.1.176.dist-info → jarvis_ai_assistant-0.1.178.dist-info}/WHEEL +1 -1
- jarvis/jarvis_lsp/base.py +0 -66
- jarvis/jarvis_lsp/cpp.py +0 -99
- jarvis/jarvis_lsp/go.py +0 -104
- jarvis/jarvis_lsp/python.py +0 -58
- jarvis/jarvis_lsp/registry.py +0 -169
- jarvis/jarvis_lsp/rust.py +0 -107
- jarvis/jarvis_tools/lsp_get_diagnostics.py +0 -147
- {jarvis_ai_assistant-0.1.176.dist-info → jarvis_ai_assistant-0.1.178.dist-info}/entry_points.txt +0 -0
- {jarvis_ai_assistant-0.1.176.dist-info → jarvis_ai_assistant-0.1.178.dist-info}/licenses/LICENSE +0 -0
- {jarvis_ai_assistant-0.1.176.dist-info → jarvis_ai_assistant-0.1.178.dist-info}/top_level.txt +0 -0
|
@@ -5,6 +5,8 @@ import os
|
|
|
5
5
|
import sys
|
|
6
6
|
from typing import Optional
|
|
7
7
|
|
|
8
|
+
from sympy import false
|
|
9
|
+
|
|
8
10
|
from jarvis.jarvis_platform.registry import PlatformRegistry
|
|
9
11
|
from jarvis.jarvis_utils.config import get_shell_name
|
|
10
12
|
from jarvis.jarvis_utils.input import get_multiline_input
|
|
@@ -113,8 +115,8 @@ def process_request(request: str) -> Optional[str]:
|
|
|
113
115
|
return None
|
|
114
116
|
|
|
115
117
|
def main() -> int:
|
|
116
|
-
# 创建参数解析器
|
|
117
|
-
init_env()
|
|
118
|
+
# 创建参数解析器s
|
|
119
|
+
init_env("")
|
|
118
120
|
parser = argparse.ArgumentParser(
|
|
119
121
|
description="将自然语言要求转换为shell命令",
|
|
120
122
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
@@ -96,7 +96,7 @@ class AskCodebaseTool:
|
|
|
96
96
|
name=f"CodebaseAnalyzer",
|
|
97
97
|
description=f"分析代码库中的功能实现和定位",
|
|
98
98
|
summary_prompt=summary_prompt,
|
|
99
|
-
platform=PlatformRegistry().
|
|
99
|
+
platform=PlatformRegistry().get_thinking_platform(),
|
|
100
100
|
output_handler=[tool_registry],
|
|
101
101
|
execute_tool_confirm=False,
|
|
102
102
|
auto_complete=self.auto_complete
|
|
@@ -160,6 +160,7 @@ class AskCodebaseTool:
|
|
|
160
160
|
5. 根据文件内容提供具体、准确的回答
|
|
161
161
|
6. 确保分析的完整性,收集充分的信息后再得出结论,不要在只掌握部分信息就得出结论
|
|
162
162
|
7. 优先查阅README文件、文档目录和项目文档
|
|
163
|
+
8. 给出的任意结论都要有实际的代码支撑,不能假设和杜撰
|
|
163
164
|
|
|
164
165
|
## 分析步骤
|
|
165
166
|
1. **确定项目的编程语言**:
|
|
@@ -259,7 +260,7 @@ def main():
|
|
|
259
260
|
import sys
|
|
260
261
|
from jarvis.jarvis_utils.input import get_multiline_input
|
|
261
262
|
|
|
262
|
-
init_env()
|
|
263
|
+
init_env("欢迎使用 Jarvis-AskCodebase,您的智能代码库查询工具已准备就绪!")
|
|
263
264
|
|
|
264
265
|
# 创建命令行参数解析器
|
|
265
266
|
parser = argparse.ArgumentParser(description="智能代码库查询工具")
|
jarvis/jarvis_tools/cli/main.py
CHANGED
|
@@ -29,7 +29,7 @@ def main() -> int:
|
|
|
29
29
|
import argparse
|
|
30
30
|
import json
|
|
31
31
|
|
|
32
|
-
init_env()
|
|
32
|
+
init_env("欢迎使用 Jarvis-Tools,您的工具系统已准备就绪!")
|
|
33
33
|
|
|
34
34
|
parser = argparse.ArgumentParser(description="Jarvis 工具系统命令行界面")
|
|
35
35
|
subparsers = parser.add_subparsers(dest="command", help="命令")
|
|
@@ -47,6 +47,10 @@ def main() -> int:
|
|
|
47
47
|
"--args-file", type=str, help="从文件加载工具参数 (JSON格式)"
|
|
48
48
|
)
|
|
49
49
|
|
|
50
|
+
# 统计子命令
|
|
51
|
+
stat_parser = subparsers.add_parser("stat", help="显示工具调用统计信息")
|
|
52
|
+
stat_parser.add_argument("--json", action="store_true", help="以JSON格式输出")
|
|
53
|
+
|
|
50
54
|
args = parser.parse_args()
|
|
51
55
|
|
|
52
56
|
# 初始化工具注册表
|
|
@@ -72,6 +76,29 @@ def main() -> int:
|
|
|
72
76
|
print(f" 参数:")
|
|
73
77
|
print(tool["parameters"]) # 显示详细参数信息
|
|
74
78
|
|
|
79
|
+
elif args.command == "stat":
|
|
80
|
+
from tabulate import tabulate
|
|
81
|
+
stats = registry._get_tool_stats()
|
|
82
|
+
tools = registry.get_all_tools()
|
|
83
|
+
|
|
84
|
+
# 构建统计表格数据
|
|
85
|
+
table_data = []
|
|
86
|
+
for tool in tools:
|
|
87
|
+
name = tool["name"]
|
|
88
|
+
count = stats.get(name, 0)
|
|
89
|
+
table_data.append([name, count])
|
|
90
|
+
|
|
91
|
+
# 按调用次数降序排序
|
|
92
|
+
table_data.sort(key=lambda x: x[1], reverse=True)
|
|
93
|
+
|
|
94
|
+
if args.json:
|
|
95
|
+
print(json.dumps(dict(table_data), indent=2))
|
|
96
|
+
else:
|
|
97
|
+
PrettyOutput.section("工具调用统计", OutputType.SYSTEM)
|
|
98
|
+
print(tabulate(table_data, headers=["工具名称", "调用次数"], tablefmt="grid"))
|
|
99
|
+
|
|
100
|
+
return 0
|
|
101
|
+
|
|
75
102
|
elif args.command == "call":
|
|
76
103
|
tool_name = args.tool_name
|
|
77
104
|
tool_obj = registry.get_tool(tool_name)
|
jarvis/jarvis_tools/edit_file.py
CHANGED
|
@@ -1,24 +1,29 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
"""
|
|
3
|
-
|
|
3
|
+
文件编辑工具类
|
|
4
4
|
|
|
5
5
|
功能概述:
|
|
6
|
-
1.
|
|
6
|
+
1. 提供精确的文件内容搜索和替换功能,支持多组修改
|
|
7
7
|
2. 支持单个文件的编辑操作,包括创建新文件
|
|
8
8
|
3. 实现原子操作:所有修改要么全部成功,要么全部回滚
|
|
9
9
|
4. 严格匹配控制:每个搜索文本必须且只能匹配一次
|
|
10
|
+
5. 支持两种编辑模式:快速编辑(fast_edit)和AI辅助编辑(slow_edit)
|
|
10
11
|
|
|
11
12
|
核心特性:
|
|
12
13
|
- 支持不存在的文件和空文件处理
|
|
13
14
|
- 自动创建所需目录结构
|
|
14
15
|
- 完善的错误处理和回滚机制
|
|
15
16
|
- 严格的格式保持要求
|
|
17
|
+
- 支持大文件处理(自动上传到模型平台)
|
|
18
|
+
- 提供3次重试机制确保操作可靠性
|
|
16
19
|
"""
|
|
20
|
+
from typing import List
|
|
17
21
|
import re
|
|
18
22
|
from typing import Any, Dict, Tuple
|
|
19
23
|
|
|
20
24
|
import yaml
|
|
21
25
|
from yaspin import yaspin
|
|
26
|
+
from yaspin.core import Yaspin
|
|
22
27
|
|
|
23
28
|
from jarvis.jarvis_platform.registry import PlatformRegistry
|
|
24
29
|
from jarvis.jarvis_tools.file_operation import FileOperationTool
|
|
@@ -30,32 +35,26 @@ from jarvis.jarvis_utils.utils import is_context_overflow
|
|
|
30
35
|
|
|
31
36
|
class FileSearchReplaceTool:
|
|
32
37
|
name = "edit_file"
|
|
33
|
-
description = """
|
|
38
|
+
description = """代码编辑工具,用于精确修改单个文件
|
|
34
39
|
|
|
35
|
-
#
|
|
40
|
+
# 文件编辑工具使用指南
|
|
36
41
|
|
|
37
|
-
## 重要提示
|
|
38
|
-
此工具可以查看和修改单个文件的代码,只需提供要修改的代码片段即可。应尽量精简内容,只包含必要的上下文和修改部分。特别注意:不要提供完整文件内容,只提供需要修改的部分及其上下文!
|
|
39
42
|
|
|
40
43
|
## 基本使用
|
|
41
44
|
1. 指定需要修改的文件路径
|
|
42
|
-
2.
|
|
43
|
-
|
|
45
|
+
2. 提供一组或多组修改,每个修改包含:
|
|
46
|
+
- reason: 修改原因描述
|
|
47
|
+
- search: 需要查找的原始代码(必须包含足够上下文)
|
|
48
|
+
- replace: 替换后的新代码
|
|
49
|
+
3. 工具会自动选择最适合的编辑模式
|
|
44
50
|
|
|
45
51
|
## 核心原则
|
|
46
|
-
1.
|
|
47
|
-
2.
|
|
48
|
-
3.
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
2. 不要出现未实现的代码,如:TODO
|
|
53
|
-
3. 示例格式:
|
|
54
|
-
```
|
|
55
|
-
# 原有上下文行
|
|
56
|
-
if condition: # 修改这行
|
|
57
|
-
return new_value
|
|
58
|
-
```
|
|
52
|
+
1. **精准修改**: 只修改必要的代码部分,保持其他部分不变
|
|
53
|
+
2. **最小补丁原则**: 生成最小范围的补丁,包含必要的上下文
|
|
54
|
+
3. **唯一匹配**: 确保搜索文本在文件中唯一匹配
|
|
55
|
+
4. **格式保持**: 严格保持原始代码的格式风格
|
|
56
|
+
|
|
57
|
+
|
|
59
58
|
"""
|
|
60
59
|
parameters = {
|
|
61
60
|
"type": "object",
|
|
@@ -74,12 +73,15 @@ class FileSearchReplaceTool:
|
|
|
74
73
|
"type": "string",
|
|
75
74
|
"description": "修改的原因"
|
|
76
75
|
},
|
|
77
|
-
"
|
|
76
|
+
"search": {
|
|
77
|
+
"type": "string",
|
|
78
|
+
"description": "需要查找的原始代码"
|
|
79
|
+
},
|
|
80
|
+
"replace": {
|
|
78
81
|
"type": "string",
|
|
79
|
-
"description": "
|
|
82
|
+
"description": "替换后的新代码"
|
|
80
83
|
}
|
|
81
84
|
},
|
|
82
|
-
"required": ["reason", "patch"]
|
|
83
85
|
}
|
|
84
86
|
}
|
|
85
87
|
},
|
|
@@ -91,24 +93,26 @@ class FileSearchReplaceTool:
|
|
|
91
93
|
pass
|
|
92
94
|
|
|
93
95
|
def execute(self, args: Dict) -> Dict[str, Any]:
|
|
94
|
-
"""
|
|
96
|
+
"""执行文件编辑操作,支持快速编辑和AI辅助编辑两种模式。
|
|
95
97
|
|
|
96
98
|
主要功能:
|
|
97
|
-
1.
|
|
99
|
+
1. 处理文件创建或修改,支持不存在的文件
|
|
98
100
|
2. 原子操作:所有修改要么全部成功,要么全部回滚
|
|
99
|
-
3.
|
|
100
|
-
4.
|
|
101
|
+
3. 自动选择编辑模式(fast_edit或slow_edit)
|
|
102
|
+
4. 保存修改前后的文件状态以便回滚
|
|
103
|
+
5. 提供详细的执行状态输出
|
|
101
104
|
|
|
102
105
|
参数:
|
|
103
106
|
args: 包含以下键的字典:
|
|
104
|
-
- file: 要修改的文件路径
|
|
105
|
-
- changes:
|
|
107
|
+
- file: 要修改的文件路径(必填)
|
|
108
|
+
- changes: 修改列表,每个修改包含(必填):
|
|
106
109
|
- reason: 修改原因描述
|
|
107
|
-
-
|
|
110
|
+
- search: 需要查找的原始代码(必须包含足够上下文)
|
|
111
|
+
- replace: 替换后的新代码
|
|
108
112
|
|
|
109
113
|
返回:
|
|
110
114
|
Dict[str, Any] 包含:
|
|
111
|
-
- success: 操作是否成功
|
|
115
|
+
- success: 操作是否成功(True/False)
|
|
112
116
|
- stdout: 成功时的输出消息
|
|
113
117
|
- stderr: 失败时的错误消息
|
|
114
118
|
|
|
@@ -116,6 +120,14 @@ class FileSearchReplaceTool:
|
|
|
116
120
|
1. 捕获并记录文件操作异常
|
|
117
121
|
2. 失败的修改尝试回滚到原始状态
|
|
118
122
|
3. 新创建的文件在失败时会被删除
|
|
123
|
+
4. 提供3次重试机制确保操作可靠性
|
|
124
|
+
5. 支持大文件处理(自动上传到模型平台)
|
|
125
|
+
|
|
126
|
+
实现细节:
|
|
127
|
+
1. 优先尝试fast_edit模式
|
|
128
|
+
2. 如果fast_edit失败,则尝试slow_edit模式
|
|
129
|
+
3. 严格检查搜索文本的唯一匹配性
|
|
130
|
+
4. 保持原始代码的格式风格
|
|
119
131
|
"""
|
|
120
132
|
import os
|
|
121
133
|
from jarvis.jarvis_utils.output import PrettyOutput, OutputType
|
|
@@ -141,8 +153,10 @@ class FileSearchReplaceTool:
|
|
|
141
153
|
with open(file_path, 'r', encoding='utf-8') as f:
|
|
142
154
|
content = f.read()
|
|
143
155
|
original_content = content
|
|
144
|
-
|
|
145
|
-
|
|
156
|
+
with yaspin(text=f"正在处理文件 {file_path}...", color="cyan") as spinner:
|
|
157
|
+
success, temp_content = fast_edit(file_path, changes, spinner)
|
|
158
|
+
if not success:
|
|
159
|
+
success, temp_content = slow_edit(file_path, yaml.safe_dump(changes), spinner)
|
|
146
160
|
|
|
147
161
|
# 只有当所有替换操作都成功时,才写回文件
|
|
148
162
|
if success and (temp_content != original_content or not file_exists):
|
|
@@ -207,29 +221,42 @@ class FileSearchReplaceTool:
|
|
|
207
221
|
"stdout": "",
|
|
208
222
|
"stderr": error_msg + "\n" + "\n".join(stderr_messages)
|
|
209
223
|
}
|
|
224
|
+
|
|
210
225
|
|
|
211
226
|
|
|
212
|
-
def
|
|
227
|
+
def slow_edit(filepath: str, patch_content: str, spinner: Yaspin) -> Tuple[bool, str]:
|
|
213
228
|
"""执行精确的文件编辑操作,使用AI模型生成差异补丁并应用。
|
|
214
|
-
|
|
215
|
-
|
|
229
|
+
|
|
230
|
+
核心功能:
|
|
216
231
|
1. 使用AI模型分析补丁内容并生成精确的代码差异
|
|
217
232
|
2. 应用生成的差异补丁到目标文件
|
|
218
|
-
3.
|
|
219
|
-
|
|
233
|
+
3. 提供3次重试机制确保操作可靠性
|
|
234
|
+
4. 支持大文件处理(自动上传到模型平台)
|
|
235
|
+
5. 严格的格式一致性检查
|
|
236
|
+
|
|
220
237
|
参数:
|
|
221
|
-
filepath: 要编辑的文件路径
|
|
222
|
-
patch_content: YAML
|
|
223
|
-
|
|
238
|
+
filepath: 要编辑的文件路径(绝对或相对路径)
|
|
239
|
+
patch_content: YAML格式的补丁内容,包含:
|
|
240
|
+
- reason: 修改原因描述
|
|
241
|
+
- search: 需要查找的原始代码(必须包含足够上下文)
|
|
242
|
+
- replace: 替换后的新代码
|
|
243
|
+
spinner: Yaspin实例,用于显示处理状态
|
|
244
|
+
|
|
224
245
|
返回值:
|
|
225
246
|
Tuple[bool, str]:
|
|
226
|
-
- 第一个元素表示操作是否成功
|
|
227
|
-
- 第二个元素是修改后的文件内容(成功时)
|
|
228
|
-
|
|
247
|
+
- 第一个元素表示操作是否成功(True/False)
|
|
248
|
+
- 第二个元素是修改后的文件内容(成功时)或空字符串(失败时)
|
|
249
|
+
|
|
229
250
|
异常处理:
|
|
230
|
-
1.
|
|
231
|
-
2.
|
|
232
|
-
3.
|
|
251
|
+
1. 文件不存在或权限不足时会捕获异常并返回失败
|
|
252
|
+
2. 模型生成补丁失败时会自动重试最多3次
|
|
253
|
+
3. 补丁应用失败时会自动回滚文件修改
|
|
254
|
+
|
|
255
|
+
实现细节:
|
|
256
|
+
1. 检查文件是否在工作目录下(影响版本控制)
|
|
257
|
+
2. 根据文件大小决定是否上传到模型平台
|
|
258
|
+
3. 使用精确的DIFF格式解析模型生成的补丁
|
|
259
|
+
4. 确保补丁应用前进行唯一性匹配检查
|
|
233
260
|
"""
|
|
234
261
|
import os
|
|
235
262
|
work_dir = os.path.abspath(os.curdir)
|
|
@@ -237,20 +264,19 @@ def patch_apply(filepath: str, patch_content: str) -> Tuple[bool, str]:
|
|
|
237
264
|
if not filepath.startswith(work_dir):
|
|
238
265
|
PrettyOutput.print(f"文件 {filepath} 不在工作目录 {work_dir} 下,不会进行版本控制管理", OutputType.WARNING)
|
|
239
266
|
model = PlatformRegistry().get_normal_platform()
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
upload_success = True
|
|
267
|
+
try:
|
|
268
|
+
file_content = FileOperationTool().execute({"operation":"read", "files":[{"path":filepath}]})["stdout"]
|
|
269
|
+
need_upload_file = is_context_overflow(file_content)
|
|
270
|
+
upload_success = False
|
|
271
|
+
# 读取原始文件内容
|
|
272
|
+
with spinner.hidden():
|
|
273
|
+
if need_upload_file and model.upload_files([filepath]):
|
|
274
|
+
upload_success = True
|
|
249
275
|
|
|
250
276
|
|
|
251
|
-
|
|
277
|
+
model.set_suppress_output(True)
|
|
252
278
|
|
|
253
|
-
|
|
279
|
+
main_prompt = f"""
|
|
254
280
|
# 代码补丁生成专家指南
|
|
255
281
|
|
|
256
282
|
## 任务描述
|
|
@@ -292,66 +318,108 @@ def patch_apply(filepath: str, patch_content: str) -> Tuple[bool, str]:
|
|
|
292
318
|
{"<" * 5} REPLACE
|
|
293
319
|
{ct("DIFF")}
|
|
294
320
|
"""
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
321
|
+
|
|
322
|
+
for _ in range(3):
|
|
323
|
+
file_prompt = ""
|
|
324
|
+
if not need_upload_file:
|
|
325
|
+
file_prompt = f"""
|
|
326
|
+
# 原始代码
|
|
327
|
+
{file_content}
|
|
328
|
+
"""
|
|
329
|
+
|
|
330
|
+
response = model.chat_until_success(main_prompt + file_prompt)
|
|
331
|
+
else:
|
|
332
|
+
if upload_success:
|
|
333
|
+
response = model.chat_until_success(main_prompt)
|
|
305
334
|
else:
|
|
306
|
-
|
|
307
|
-
response = model.chat_until_success(main_prompt)
|
|
308
|
-
else:
|
|
309
|
-
return False, ""
|
|
310
|
-
|
|
311
|
-
# 解析差异化补丁
|
|
312
|
-
diff_blocks = re.finditer(ot("DIFF")+r'\s*>{4,} SEARCH\n?(.*?)\n?={4,}\n?(.*?)\s*<{4,} REPLACE\n?'+ct("DIFF"),
|
|
313
|
-
response, re.DOTALL)
|
|
314
|
-
|
|
315
|
-
# 读取原始文件内容
|
|
316
|
-
with open(filepath, 'r', encoding='utf-8', errors="ignore") as f:
|
|
317
|
-
file_content = f.read()
|
|
318
|
-
|
|
319
|
-
# 应用所有差异化补丁
|
|
320
|
-
modified_content = file_content
|
|
321
|
-
patch_count = 0
|
|
322
|
-
success = True
|
|
323
|
-
for match in diff_blocks:
|
|
324
|
-
search_text = match.group(1).strip()
|
|
325
|
-
replace_text = match.group(2).strip()
|
|
326
|
-
patch_count += 1
|
|
327
|
-
# 检查搜索文本是否存在于文件中
|
|
328
|
-
if search_text in modified_content:
|
|
329
|
-
# 如果有多处,报错
|
|
330
|
-
if modified_content.count(search_text) > 1:
|
|
331
|
-
spinner.write(f"❌ 补丁 #{patch_count} 应用失败:找到多个匹配的代码段")
|
|
332
|
-
success = False
|
|
333
|
-
break
|
|
334
|
-
# 应用替换
|
|
335
|
-
modified_content = modified_content.replace(
|
|
336
|
-
search_text, replace_text)
|
|
337
|
-
spinner.write(f"✅ 补丁 #{patch_count} 应用成功")
|
|
338
|
-
else:
|
|
339
|
-
spinner.write(f"❌ 补丁 #{patch_count} 应用失败:无法找到匹配的代码段")
|
|
340
|
-
success = False
|
|
341
|
-
break
|
|
342
|
-
if not success:
|
|
343
|
-
revert_file(filepath)
|
|
344
|
-
continue
|
|
335
|
+
return False, ""
|
|
345
336
|
|
|
337
|
+
# 解析差异化补丁
|
|
338
|
+
diff_blocks = re.finditer(ot("DIFF")+r'\s*>{4,} SEARCH\n?(.*?)\n?={4,}\n?(.*?)\s*<{4,} REPLACE\n?'+ct("DIFF"),
|
|
339
|
+
response, re.DOTALL)
|
|
346
340
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
341
|
+
patches = []
|
|
342
|
+
for match in diff_blocks:
|
|
343
|
+
patches.append({
|
|
344
|
+
"search": match.group(1).strip(),
|
|
345
|
+
"replace": match.group(2).strip()
|
|
346
|
+
})
|
|
353
347
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
348
|
+
success, modified_content = fast_edit(filepath, patches, spinner)
|
|
349
|
+
if success:
|
|
350
|
+
return True, modified_content
|
|
351
|
+
spinner.text = f"文件 {filepath} 修改失败"
|
|
352
|
+
spinner.fail("❌")
|
|
353
|
+
return False, ""
|
|
354
|
+
|
|
355
|
+
except Exception as e:
|
|
356
|
+
spinner.text = f"文件修改失败: {str(e)}"
|
|
357
|
+
spinner.fail("❌")
|
|
358
|
+
return False, ""
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def fast_edit(filepath: str, patches: List[Dict[str,str]], spinner: Yaspin) -> Tuple[bool, str]:
|
|
362
|
+
"""快速应用预先生成的补丁到目标文件。
|
|
363
|
+
|
|
364
|
+
核心功能:
|
|
365
|
+
1. 直接应用已生成的代码补丁
|
|
366
|
+
2. 执行严格的唯一匹配检查
|
|
367
|
+
3. 提供详细的补丁应用状态反馈
|
|
368
|
+
4. 失败时自动回滚文件修改
|
|
369
|
+
|
|
370
|
+
参数:
|
|
371
|
+
filepath: 要编辑的文件路径(绝对或相对路径)
|
|
372
|
+
patches: 补丁列表,每个补丁包含:
|
|
373
|
+
- search: 需要查找的原始代码
|
|
374
|
+
- replace: 替换后的新代码
|
|
375
|
+
spinner: Yaspin实例,用于显示处理状态
|
|
376
|
+
|
|
377
|
+
返回值:
|
|
378
|
+
Tuple[bool, str]:
|
|
379
|
+
- 第一个元素表示操作是否成功(True/False)
|
|
380
|
+
- 第二个元素是修改后的文件内容(成功时)或空字符串(失败时)
|
|
381
|
+
|
|
382
|
+
异常处理:
|
|
383
|
+
1. 文件不存在或权限不足时会捕获异常并返回失败
|
|
384
|
+
2. 补丁不匹配或有多处匹配时会返回失败
|
|
385
|
+
3. 失败时会自动回滚文件修改
|
|
386
|
+
|
|
387
|
+
实现细节:
|
|
388
|
+
1. 读取文件内容到内存
|
|
389
|
+
2. 依次应用每个补丁,检查唯一匹配性
|
|
390
|
+
3. 记录每个补丁的应用状态
|
|
391
|
+
4. 所有补丁成功应用后才写入文件
|
|
392
|
+
"""
|
|
393
|
+
# 读取原始文件内容
|
|
394
|
+
with open(filepath, 'r', encoding='utf-8', errors="ignore") as f:
|
|
395
|
+
file_content = f.read()
|
|
396
|
+
|
|
397
|
+
# 应用所有差异化补丁
|
|
398
|
+
modified_content = file_content
|
|
399
|
+
patch_count = 0
|
|
400
|
+
success = True
|
|
401
|
+
for patch in patches:
|
|
402
|
+
search_text = patch["search"]
|
|
403
|
+
replace_text = patch["replace"]
|
|
404
|
+
patch_count += 1
|
|
405
|
+
# 检查搜索文本是否存在于文件中
|
|
406
|
+
if search_text in modified_content:
|
|
407
|
+
# 如果有多处,报错
|
|
408
|
+
if modified_content.count(search_text) > 1:
|
|
409
|
+
success = False
|
|
410
|
+
break
|
|
411
|
+
# 应用替换
|
|
412
|
+
modified_content = modified_content.replace(
|
|
413
|
+
search_text, replace_text)
|
|
414
|
+
spinner.write(f"✅ 补丁 #{patch_count} 应用成功")
|
|
415
|
+
else:
|
|
416
|
+
success = False
|
|
417
|
+
break
|
|
418
|
+
if not success:
|
|
419
|
+
revert_file(filepath)
|
|
420
|
+
return False, ""
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
spinner.text = f"文件 {filepath} 修改完成,应用了 {patch_count} 个补丁"
|
|
424
|
+
spinner.ok("✅")
|
|
425
|
+
return True, modified_content
|
jarvis/jarvis_tools/read_code.py
CHANGED
jarvis/jarvis_tools/registry.py
CHANGED
|
@@ -178,6 +178,32 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
|
178
178
|
self._load_external_tools()
|
|
179
179
|
self._load_mcp_tools()
|
|
180
180
|
|
|
181
|
+
def _get_tool_stats(self) -> Dict[str, int]:
|
|
182
|
+
"""从数据目录获取工具调用统计"""
|
|
183
|
+
stats_file = Path(get_data_dir()) / "tool_stat.yaml"
|
|
184
|
+
if stats_file.exists():
|
|
185
|
+
try:
|
|
186
|
+
with open(stats_file, "r", encoding="utf-8") as f:
|
|
187
|
+
return yaml.safe_load(f) or {}
|
|
188
|
+
except Exception as e:
|
|
189
|
+
PrettyOutput.print(
|
|
190
|
+
f"加载工具调用统计失败: {str(e)}", OutputType.WARNING
|
|
191
|
+
)
|
|
192
|
+
return {}
|
|
193
|
+
|
|
194
|
+
def _update_tool_stats(self, name: str) -> None:
|
|
195
|
+
"""更新工具调用统计"""
|
|
196
|
+
stats = self._get_tool_stats()
|
|
197
|
+
stats[name] = stats.get(name, 0) + 1
|
|
198
|
+
stats_file = Path(get_data_dir()) / "tool_stat.yaml"
|
|
199
|
+
try:
|
|
200
|
+
with open(stats_file, "w", encoding="utf-8") as f:
|
|
201
|
+
yaml.safe_dump(stats, f)
|
|
202
|
+
except Exception as e:
|
|
203
|
+
PrettyOutput.print(
|
|
204
|
+
f"保存工具调用统计失败: {str(e)}", OutputType.WARNING
|
|
205
|
+
)
|
|
206
|
+
|
|
181
207
|
def use_tools(self, name: List[str]) -> None:
|
|
182
208
|
"""使用指定工具
|
|
183
209
|
|
|
@@ -190,7 +216,7 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
|
190
216
|
f"工具 {missing_tools} 不存在,可用的工具有: {', '.join(self.tools.keys())}",
|
|
191
217
|
OutputType.WARNING,
|
|
192
218
|
)
|
|
193
|
-
self.tools = {tool_name: self.tools[tool_name] for tool_name in name}
|
|
219
|
+
self.tools = {tool_name: self.tools[tool_name] for tool_name in name if tool_name in self.tools}
|
|
194
220
|
|
|
195
221
|
def dont_use_tools(self, names: List[str]) -> None:
|
|
196
222
|
"""从注册表中移除指定工具
|
|
@@ -559,6 +585,10 @@ class ToolRegistry(OutputHandlerProtocol):
|
|
|
559
585
|
"stderr": f"工具 {name} 不存在,可用的工具有: {', '.join(self.tools.keys())}",
|
|
560
586
|
"stdout": "",
|
|
561
587
|
}
|
|
588
|
+
|
|
589
|
+
# 更新工具调用统计
|
|
590
|
+
self._update_tool_stats(name)
|
|
591
|
+
|
|
562
592
|
return tool.execute(arguments)
|
|
563
593
|
|
|
564
594
|
def _format_tool_output(self, stdout: str, stderr: str) -> str:
|
jarvis/jarvis_utils/config.py
CHANGED
|
@@ -38,7 +38,7 @@ def get_max_token_count() -> int:
|
|
|
38
38
|
返回:
|
|
39
39
|
int: 模型能处理的最大token数量。
|
|
40
40
|
"""
|
|
41
|
-
return int(os.getenv('JARVIS_MAX_TOKEN_COUNT', '
|
|
41
|
+
return int(os.getenv('JARVIS_MAX_TOKEN_COUNT', '960000'))
|
|
42
42
|
|
|
43
43
|
def get_max_input_token_count() -> int:
|
|
44
44
|
"""
|
|
@@ -158,9 +158,9 @@ def get_max_big_content_size() -> int:
|
|
|
158
158
|
获取最大大内容大小。
|
|
159
159
|
|
|
160
160
|
返回:
|
|
161
|
-
int:
|
|
161
|
+
int: 最大大内容大小
|
|
162
162
|
"""
|
|
163
|
-
return int(os.getenv('JARVIS_MAX_BIG_CONTENT_SIZE', '
|
|
163
|
+
return int(os.getenv('JARVIS_MAX_BIG_CONTENT_SIZE', '1024000'))
|
|
164
164
|
|
|
165
165
|
def get_pretty_output() -> bool:
|
|
166
166
|
"""
|
|
@@ -169,4 +169,13 @@ def get_pretty_output() -> bool:
|
|
|
169
169
|
返回:
|
|
170
170
|
bool: 如果启用PrettyOutput则返回True,默认为True
|
|
171
171
|
"""
|
|
172
|
-
return os.getenv('JARVIS_PRETTY_OUTPUT', 'false') == 'true'
|
|
172
|
+
return os.getenv('JARVIS_PRETTY_OUTPUT', 'false') == 'true'
|
|
173
|
+
|
|
174
|
+
def is_use_methodology() -> bool:
|
|
175
|
+
"""
|
|
176
|
+
获取是否启用方法论。
|
|
177
|
+
|
|
178
|
+
返回:
|
|
179
|
+
bool: 如果启用方法论则返回True,默认为True
|
|
180
|
+
"""
|
|
181
|
+
return os.getenv('JARVIS_USE_METHODOLOGY', 'true') == 'true'
|