jarvis-ai-assistant 0.3.30__py3-none-any.whl → 0.7.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.
- jarvis/__init__.py +1 -1
- jarvis/jarvis_agent/__init__.py +289 -87
- jarvis/jarvis_agent/agent_manager.py +17 -8
- jarvis/jarvis_agent/edit_file_handler.py +374 -86
- jarvis/jarvis_agent/event_bus.py +1 -1
- jarvis/jarvis_agent/file_context_handler.py +79 -0
- jarvis/jarvis_agent/jarvis.py +601 -43
- jarvis/jarvis_agent/main.py +32 -2
- jarvis/jarvis_agent/rewrite_file_handler.py +141 -0
- jarvis/jarvis_agent/run_loop.py +38 -5
- jarvis/jarvis_agent/share_manager.py +8 -1
- jarvis/jarvis_agent/stdio_redirect.py +295 -0
- jarvis/jarvis_agent/task_analyzer.py +5 -2
- jarvis/jarvis_agent/task_planner.py +496 -0
- jarvis/jarvis_agent/utils.py +5 -1
- jarvis/jarvis_agent/web_bridge.py +189 -0
- jarvis/jarvis_agent/web_output_sink.py +53 -0
- jarvis/jarvis_agent/web_server.py +751 -0
- jarvis/jarvis_c2rust/__init__.py +26 -0
- jarvis/jarvis_c2rust/cli.py +613 -0
- jarvis/jarvis_c2rust/collector.py +258 -0
- jarvis/jarvis_c2rust/library_replacer.py +1122 -0
- jarvis/jarvis_c2rust/llm_module_agent.py +1300 -0
- jarvis/jarvis_c2rust/optimizer.py +960 -0
- jarvis/jarvis_c2rust/scanner.py +1681 -0
- jarvis/jarvis_c2rust/transpiler.py +2325 -0
- jarvis/jarvis_code_agent/build_validation_config.py +133 -0
- jarvis/jarvis_code_agent/code_agent.py +1171 -94
- jarvis/jarvis_code_agent/code_analyzer/__init__.py +62 -0
- jarvis/jarvis_code_agent/code_analyzer/base_language.py +74 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/__init__.py +44 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/base.py +102 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/cmake.py +59 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/detector.py +125 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/fallback.py +69 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/go.py +38 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/java_gradle.py +44 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/java_maven.py +38 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/makefile.py +50 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/nodejs.py +93 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/python.py +129 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/rust.py +54 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/validator.py +154 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator.py +43 -0
- jarvis/jarvis_code_agent/code_analyzer/context_manager.py +363 -0
- jarvis/jarvis_code_agent/code_analyzer/context_recommender.py +18 -0
- jarvis/jarvis_code_agent/code_analyzer/dependency_analyzer.py +132 -0
- jarvis/jarvis_code_agent/code_analyzer/file_ignore.py +330 -0
- jarvis/jarvis_code_agent/code_analyzer/impact_analyzer.py +781 -0
- jarvis/jarvis_code_agent/code_analyzer/language_registry.py +185 -0
- jarvis/jarvis_code_agent/code_analyzer/language_support.py +89 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/__init__.py +31 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/c_cpp_language.py +231 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/go_language.py +183 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/python_language.py +219 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/rust_language.py +209 -0
- jarvis/jarvis_code_agent/code_analyzer/llm_context_recommender.py +451 -0
- jarvis/jarvis_code_agent/code_analyzer/symbol_extractor.py +77 -0
- jarvis/jarvis_code_agent/code_analyzer/tree_sitter_extractor.py +48 -0
- jarvis/jarvis_code_agent/lint.py +270 -8
- jarvis/jarvis_code_agent/utils.py +142 -0
- jarvis/jarvis_code_analysis/code_review.py +483 -569
- jarvis/jarvis_data/config_schema.json +97 -8
- jarvis/jarvis_git_utils/git_commiter.py +38 -26
- jarvis/jarvis_mcp/sse_mcp_client.py +2 -2
- jarvis/jarvis_mcp/stdio_mcp_client.py +1 -1
- jarvis/jarvis_memory_organizer/memory_organizer.py +1 -1
- jarvis/jarvis_multi_agent/__init__.py +239 -25
- jarvis/jarvis_multi_agent/main.py +37 -1
- jarvis/jarvis_platform/base.py +103 -51
- jarvis/jarvis_platform/openai.py +26 -1
- jarvis/jarvis_platform/yuanbao.py +1 -1
- jarvis/jarvis_platform_manager/service.py +2 -2
- jarvis/jarvis_rag/cli.py +4 -4
- jarvis/jarvis_sec/__init__.py +3605 -0
- jarvis/jarvis_sec/checkers/__init__.py +32 -0
- jarvis/jarvis_sec/checkers/c_checker.py +2680 -0
- jarvis/jarvis_sec/checkers/rust_checker.py +1108 -0
- jarvis/jarvis_sec/cli.py +116 -0
- jarvis/jarvis_sec/report.py +257 -0
- jarvis/jarvis_sec/status.py +264 -0
- jarvis/jarvis_sec/types.py +20 -0
- jarvis/jarvis_sec/workflow.py +219 -0
- jarvis/jarvis_stats/cli.py +1 -1
- jarvis/jarvis_stats/stats.py +1 -1
- jarvis/jarvis_stats/visualizer.py +1 -1
- jarvis/jarvis_tools/cli/main.py +1 -0
- jarvis/jarvis_tools/execute_script.py +46 -9
- jarvis/jarvis_tools/generate_new_tool.py +3 -1
- jarvis/jarvis_tools/read_code.py +275 -12
- jarvis/jarvis_tools/read_symbols.py +141 -0
- jarvis/jarvis_tools/read_webpage.py +5 -3
- jarvis/jarvis_tools/registry.py +73 -35
- jarvis/jarvis_tools/search_web.py +15 -11
- jarvis/jarvis_tools/sub_agent.py +24 -42
- jarvis/jarvis_tools/sub_code_agent.py +14 -13
- jarvis/jarvis_tools/virtual_tty.py +1 -1
- jarvis/jarvis_utils/config.py +187 -35
- jarvis/jarvis_utils/embedding.py +3 -0
- jarvis/jarvis_utils/git_utils.py +181 -6
- jarvis/jarvis_utils/globals.py +3 -3
- jarvis/jarvis_utils/http.py +1 -1
- jarvis/jarvis_utils/input.py +78 -2
- jarvis/jarvis_utils/methodology.py +25 -19
- jarvis/jarvis_utils/utils.py +644 -359
- {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/METADATA +85 -1
- jarvis_ai_assistant-0.7.0.dist-info/RECORD +192 -0
- {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/entry_points.txt +4 -0
- jarvis/jarvis_agent/config.py +0 -92
- jarvis/jarvis_tools/edit_file.py +0 -179
- jarvis/jarvis_tools/rewrite_file.py +0 -191
- jarvis_ai_assistant-0.3.30.dist-info/RECORD +0 -137
- {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/licenses/LICENSE +0 -0
- {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/top_level.txt +0 -0
jarvis/jarvis_tools/read_code.py
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
import os
|
|
3
3
|
from typing import Any, Dict
|
|
4
4
|
|
|
5
|
+
from jarvis.jarvis_utils.config import get_max_input_token_count
|
|
6
|
+
from jarvis.jarvis_utils.embedding import get_context_token_count
|
|
5
7
|
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
|
6
8
|
|
|
7
9
|
|
|
@@ -28,6 +30,29 @@ class ReadCodeTool:
|
|
|
28
30
|
},
|
|
29
31
|
"required": ["files"],
|
|
30
32
|
}
|
|
33
|
+
|
|
34
|
+
def _get_max_token_limit(self, agent: Any = None) -> int:
|
|
35
|
+
"""获取基于最大窗口数量的token限制
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
agent: Agent实例,用于获取模型组配置
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
int: 允许的最大token数(2/3最大窗口)
|
|
42
|
+
"""
|
|
43
|
+
try:
|
|
44
|
+
# 尝试从agent获取模型组
|
|
45
|
+
model_group = None
|
|
46
|
+
if agent:
|
|
47
|
+
model_group = getattr(agent, "model_group", None)
|
|
48
|
+
|
|
49
|
+
max_input_tokens = get_max_input_token_count(model_group)
|
|
50
|
+
# 计算2/3限制的token数
|
|
51
|
+
limit_tokens = int(max_input_tokens * 2 / 3)
|
|
52
|
+
return limit_tokens
|
|
53
|
+
except Exception:
|
|
54
|
+
# 如果获取失败,使用默认值(假设32000 token,2/3是21333)
|
|
55
|
+
return 21333
|
|
31
56
|
|
|
32
57
|
def _handle_single_file(
|
|
33
58
|
self, filepath: str, start_line: int = 1, end_line: int = -1, agent: Any = None
|
|
@@ -38,6 +63,7 @@ class ReadCodeTool:
|
|
|
38
63
|
filepath (str): 文件路径
|
|
39
64
|
start_line (int): 起始行号,默认为1
|
|
40
65
|
end_line (int): 结束行号,默认为-1表示文件末尾
|
|
66
|
+
agent: Agent实例,用于获取上下文管理器
|
|
41
67
|
|
|
42
68
|
Returns:
|
|
43
69
|
Dict[str, Any]: 包含成功状态、输出内容和错误信息的字典
|
|
@@ -62,10 +88,9 @@ class ReadCodeTool:
|
|
|
62
88
|
}
|
|
63
89
|
|
|
64
90
|
# 读取文件内容
|
|
91
|
+
# 第一遍流式读取,仅统计总行数,避免一次性读入内存
|
|
65
92
|
with open(abs_path, "r", encoding="utf-8", errors="ignore") as f:
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
total_lines = len(lines)
|
|
93
|
+
total_lines = sum(1 for _ in f)
|
|
69
94
|
|
|
70
95
|
# 处理空文件情况
|
|
71
96
|
if total_lines == 0:
|
|
@@ -99,14 +124,41 @@ class ReadCodeTool:
|
|
|
99
124
|
"stderr": f"无效的行范围 [{start_line}-{end_line}] (总行数: {total_lines})",
|
|
100
125
|
}
|
|
101
126
|
|
|
102
|
-
#
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
127
|
+
# 读取要读取的行范围内容,计算实际token数
|
|
128
|
+
selected_content_lines = []
|
|
129
|
+
with open(abs_path, "r", encoding="utf-8", errors="ignore") as f:
|
|
130
|
+
for i, line in enumerate(f, start=1):
|
|
131
|
+
if i < start_line:
|
|
132
|
+
continue
|
|
133
|
+
if i > end_line:
|
|
134
|
+
break
|
|
135
|
+
selected_content_lines.append(line)
|
|
136
|
+
|
|
137
|
+
# 构建带行号的内容用于token计算(与实际输出格式一致)
|
|
138
|
+
numbered_content = "".join(f"{i:4d}:{line}" for i, line in enumerate(selected_content_lines, start=start_line))
|
|
139
|
+
|
|
140
|
+
# 计算实际token数
|
|
141
|
+
content_tokens = get_context_token_count(numbered_content)
|
|
142
|
+
max_token_limit = self._get_max_token_limit(agent)
|
|
143
|
+
|
|
144
|
+
# 检查单文件读取token数是否超过2/3限制
|
|
145
|
+
if content_tokens > max_token_limit:
|
|
146
|
+
read_lines = end_line - start_line + 1
|
|
147
|
+
return {
|
|
148
|
+
"success": False,
|
|
149
|
+
"stdout": "",
|
|
150
|
+
"stderr": (
|
|
151
|
+
f"⚠️ 读取范围过大: 请求读取内容约 {content_tokens} tokens,超过限制 ({max_token_limit} tokens,约2/3最大窗口)\n"
|
|
152
|
+
f"📊 读取范围: {read_lines} 行 (第 {start_line}-{end_line} 行,文件总行数 {total_lines})\n"
|
|
153
|
+
f"💡 建议:\n"
|
|
154
|
+
f" 1. 分批读取:将范围分成多个较小的批次,每批内容不超过 {max_token_limit} tokens\n"
|
|
155
|
+
f" 2. 先定位:使用搜索或分析工具定位大致位置,再读取具体范围\n"
|
|
156
|
+
f" 3. 缩小范围:为文件指定更精确的行号范围"
|
|
157
|
+
),
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
# 使用已读取的内容构建输出(避免重复读取)
|
|
161
|
+
numbered_content = "".join(f"{i:4d}:{line}" for i, line in enumerate(selected_content_lines, start=start_line))
|
|
110
162
|
|
|
111
163
|
# 构建输出格式
|
|
112
164
|
output = (
|
|
@@ -115,6 +167,11 @@ class ReadCodeTool:
|
|
|
115
167
|
f"{numbered_content}\n\n"
|
|
116
168
|
)
|
|
117
169
|
|
|
170
|
+
# 尝试获取并附加上下文信息
|
|
171
|
+
context_info = self._get_file_context(abs_path, start_line, end_line, agent)
|
|
172
|
+
if context_info:
|
|
173
|
+
output += context_info
|
|
174
|
+
|
|
118
175
|
if agent:
|
|
119
176
|
files = agent.get_user_data("files")
|
|
120
177
|
if files:
|
|
@@ -129,6 +186,105 @@ class ReadCodeTool:
|
|
|
129
186
|
PrettyOutput.print(str(e), OutputType.ERROR)
|
|
130
187
|
return {"success": False, "stdout": "", "stderr": f"文件读取失败: {str(e)}"}
|
|
131
188
|
|
|
189
|
+
def _get_file_context(
|
|
190
|
+
self, filepath: str, start_line: int, end_line: int, agent: Any = None
|
|
191
|
+
) -> str:
|
|
192
|
+
"""获取文件的上下文信息
|
|
193
|
+
|
|
194
|
+
Args:
|
|
195
|
+
filepath: 文件路径
|
|
196
|
+
start_line: 起始行号
|
|
197
|
+
end_line: 结束行号
|
|
198
|
+
agent: Agent实例
|
|
199
|
+
|
|
200
|
+
Returns:
|
|
201
|
+
格式化的上下文信息字符串,如果无法获取则返回空字符串
|
|
202
|
+
"""
|
|
203
|
+
try:
|
|
204
|
+
# 尝试从Agent获取CodeAgent实例
|
|
205
|
+
if not agent:
|
|
206
|
+
return ""
|
|
207
|
+
|
|
208
|
+
# 通过agent获取CodeAgent实例
|
|
209
|
+
# CodeAgent在初始化时会将自身关联到agent
|
|
210
|
+
code_agent = getattr(agent, "_code_agent", None)
|
|
211
|
+
if not code_agent:
|
|
212
|
+
return ""
|
|
213
|
+
|
|
214
|
+
# 获取上下文管理器
|
|
215
|
+
context_manager = getattr(code_agent, "context_manager", None)
|
|
216
|
+
if not context_manager:
|
|
217
|
+
return ""
|
|
218
|
+
|
|
219
|
+
# 输出上下文感知日志
|
|
220
|
+
file_name = os.path.basename(filepath)
|
|
221
|
+
if start_line == end_line:
|
|
222
|
+
line_info = f"第{start_line}行"
|
|
223
|
+
else:
|
|
224
|
+
line_info = f"第{start_line}-{end_line}行"
|
|
225
|
+
PrettyOutput.print(f"🧠 正在分析代码上下文 ({file_name}, {line_info})...", OutputType.INFO)
|
|
226
|
+
|
|
227
|
+
# 确保文件已更新到上下文管理器
|
|
228
|
+
# 如果文件内容已缓存,直接使用;否则读取并更新
|
|
229
|
+
if not hasattr(context_manager, "_file_cache") or filepath not in context_manager._file_cache:
|
|
230
|
+
try:
|
|
231
|
+
with open(filepath, "r", encoding="utf-8", errors="replace") as f:
|
|
232
|
+
content = f.read()
|
|
233
|
+
context_manager.update_context_for_file(filepath, content)
|
|
234
|
+
except Exception:
|
|
235
|
+
# 如果读取失败,尝试获取已有上下文
|
|
236
|
+
pass
|
|
237
|
+
|
|
238
|
+
# 获取编辑上下文
|
|
239
|
+
edit_context = context_manager.get_edit_context(filepath, start_line, end_line)
|
|
240
|
+
|
|
241
|
+
# 构建上下文信息
|
|
242
|
+
if not edit_context.context_summary or edit_context.context_summary == "No context available":
|
|
243
|
+
return ""
|
|
244
|
+
|
|
245
|
+
# 格式化上下文信息
|
|
246
|
+
context_lines = ["\n📋 代码上下文信息:"]
|
|
247
|
+
context_lines.append("─" * 60)
|
|
248
|
+
|
|
249
|
+
if edit_context.current_scope:
|
|
250
|
+
scope_info = f"📍 当前作用域: {edit_context.current_scope.kind} `{edit_context.current_scope.name}`"
|
|
251
|
+
if edit_context.current_scope.signature:
|
|
252
|
+
scope_info += f"\n └─ 签名: {edit_context.current_scope.signature}"
|
|
253
|
+
context_lines.append(scope_info)
|
|
254
|
+
|
|
255
|
+
if edit_context.used_symbols:
|
|
256
|
+
symbol_names = [s.name for s in edit_context.used_symbols[:10]]
|
|
257
|
+
symbols_str = ", ".join(f"`{name}`" for name in symbol_names)
|
|
258
|
+
more = len(edit_context.used_symbols) - 10
|
|
259
|
+
if more > 0:
|
|
260
|
+
symbols_str += f" (还有{more}个)"
|
|
261
|
+
context_lines.append(f"🔗 使用的符号: {symbols_str}")
|
|
262
|
+
|
|
263
|
+
if edit_context.imported_symbols:
|
|
264
|
+
import_names = [s.name for s in edit_context.imported_symbols[:10]]
|
|
265
|
+
imports_str = ", ".join(f"`{name}`" for name in import_names)
|
|
266
|
+
more = len(edit_context.imported_symbols) - 10
|
|
267
|
+
if more > 0:
|
|
268
|
+
imports_str += f" (还有{more}个)"
|
|
269
|
+
context_lines.append(f"📦 导入的符号: {imports_str}")
|
|
270
|
+
|
|
271
|
+
if edit_context.relevant_files:
|
|
272
|
+
rel_files = edit_context.relevant_files[:10]
|
|
273
|
+
files_str = "\n ".join(f"• {os.path.relpath(f, context_manager.project_root)}" for f in rel_files)
|
|
274
|
+
more = len(edit_context.relevant_files) - 10
|
|
275
|
+
if more > 0:
|
|
276
|
+
files_str += f"\n ... 还有{more}个相关文件"
|
|
277
|
+
context_lines.append(f"📁 相关文件 ({len(edit_context.relevant_files)}个):\n {files_str}")
|
|
278
|
+
|
|
279
|
+
context_lines.append("─" * 60)
|
|
280
|
+
context_lines.append("") # 空行
|
|
281
|
+
|
|
282
|
+
return "\n".join(context_lines)
|
|
283
|
+
|
|
284
|
+
except Exception:
|
|
285
|
+
# 静默失败,不影响文件读取
|
|
286
|
+
return ""
|
|
287
|
+
|
|
132
288
|
def execute(self, args: Dict) -> Dict[str, Any]:
|
|
133
289
|
"""执行代码读取操作
|
|
134
290
|
|
|
@@ -149,7 +305,105 @@ class ReadCodeTool:
|
|
|
149
305
|
|
|
150
306
|
all_outputs = []
|
|
151
307
|
overall_success = True
|
|
308
|
+
status_lines = []
|
|
309
|
+
total_tokens = 0 # 累计读取的token数
|
|
310
|
+
max_token_limit = self._get_max_token_limit(agent)
|
|
311
|
+
|
|
312
|
+
# 第一遍:检查所有文件的累计token数是否超过限制
|
|
313
|
+
file_read_info = [] # 存储每个文件要读取的信息
|
|
314
|
+
for file_info in args["files"]:
|
|
315
|
+
if not isinstance(file_info, dict) or "path" not in file_info:
|
|
316
|
+
continue
|
|
317
|
+
|
|
318
|
+
filepath = file_info["path"].strip()
|
|
319
|
+
start_line = file_info.get("start_line", 1)
|
|
320
|
+
end_line = file_info.get("end_line", -1)
|
|
321
|
+
|
|
322
|
+
# 检查文件是否存在并计算要读取的token数
|
|
323
|
+
abs_path = os.path.abspath(filepath)
|
|
324
|
+
if not os.path.exists(abs_path):
|
|
325
|
+
continue
|
|
326
|
+
|
|
327
|
+
try:
|
|
328
|
+
# 统计总行数
|
|
329
|
+
with open(abs_path, "r", encoding="utf-8", errors="ignore") as f:
|
|
330
|
+
total_lines = sum(1 for _ in f)
|
|
331
|
+
|
|
332
|
+
if total_lines == 0:
|
|
333
|
+
continue
|
|
334
|
+
|
|
335
|
+
# 计算实际要读取的行范围
|
|
336
|
+
if end_line == -1:
|
|
337
|
+
actual_end_line = total_lines
|
|
338
|
+
else:
|
|
339
|
+
actual_end_line = (
|
|
340
|
+
max(1, min(end_line, total_lines))
|
|
341
|
+
if end_line >= 0
|
|
342
|
+
else total_lines + end_line + 1
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
actual_start_line = (
|
|
346
|
+
max(1, min(start_line, total_lines))
|
|
347
|
+
if start_line >= 0
|
|
348
|
+
else total_lines + start_line + 1
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
if actual_start_line <= actual_end_line:
|
|
352
|
+
# 读取要读取的行范围内容
|
|
353
|
+
selected_content_lines = []
|
|
354
|
+
with open(abs_path, "r", encoding="utf-8", errors="ignore") as f:
|
|
355
|
+
for i, line in enumerate(f, start=1):
|
|
356
|
+
if i < actual_start_line:
|
|
357
|
+
continue
|
|
358
|
+
if i > actual_end_line:
|
|
359
|
+
break
|
|
360
|
+
selected_content_lines.append(line)
|
|
361
|
+
|
|
362
|
+
# 构建带行号的内容用于token计算(与实际输出格式一致)
|
|
363
|
+
numbered_content = "".join(
|
|
364
|
+
f"{i:4d}:{line}"
|
|
365
|
+
for i, line in enumerate(selected_content_lines, start=actual_start_line)
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
# 计算实际token数
|
|
369
|
+
content_tokens = get_context_token_count(numbered_content)
|
|
370
|
+
|
|
371
|
+
file_read_info.append({
|
|
372
|
+
"filepath": filepath,
|
|
373
|
+
"start_line": actual_start_line,
|
|
374
|
+
"end_line": actual_end_line,
|
|
375
|
+
"read_lines": actual_end_line - actual_start_line + 1,
|
|
376
|
+
"tokens": content_tokens,
|
|
377
|
+
"file_info": file_info,
|
|
378
|
+
})
|
|
379
|
+
total_tokens += content_tokens
|
|
380
|
+
except Exception:
|
|
381
|
+
continue
|
|
382
|
+
|
|
383
|
+
# 检查累计token数是否超过限制
|
|
384
|
+
if total_tokens > max_token_limit:
|
|
385
|
+
file_list = "\n ".join(
|
|
386
|
+
f"• {info['filepath']}: {info['tokens']} tokens ({info['read_lines']} 行, 范围: {info['start_line']}-{info['end_line']})"
|
|
387
|
+
for info in file_read_info[:10]
|
|
388
|
+
)
|
|
389
|
+
more_files = len(file_read_info) - 10
|
|
390
|
+
if more_files > 0:
|
|
391
|
+
file_list += f"\n ... 还有 {more_files} 个文件"
|
|
392
|
+
|
|
393
|
+
return {
|
|
394
|
+
"success": False,
|
|
395
|
+
"stdout": "",
|
|
396
|
+
"stderr": (
|
|
397
|
+
f"⚠️ 累计读取范围过大: 请求累计读取内容约 {total_tokens} tokens,超过限制 ({max_token_limit} tokens,约2/3最大窗口)\n"
|
|
398
|
+
f"📋 文件列表 ({len(file_read_info)} 个文件):\n {file_list}\n"
|
|
399
|
+
f"💡 建议:\n"
|
|
400
|
+
f" 1. 分批读取:将文件分成多个批次,每批累计内容不超过 {max_token_limit} tokens\n"
|
|
401
|
+
f" 2. 先定位:使用搜索或分析工具定位关键代码位置,再读取具体范围\n"
|
|
402
|
+
f" 3. 缩小范围:为每个文件指定更精确的行号范围"
|
|
403
|
+
),
|
|
404
|
+
}
|
|
152
405
|
|
|
406
|
+
# 第二遍:实际读取文件
|
|
153
407
|
for file_info in args["files"]:
|
|
154
408
|
if not isinstance(file_info, dict) or "path" not in file_info:
|
|
155
409
|
continue
|
|
@@ -163,13 +417,22 @@ class ReadCodeTool:
|
|
|
163
417
|
|
|
164
418
|
if result["success"]:
|
|
165
419
|
all_outputs.append(result["stdout"])
|
|
420
|
+
status_lines.append(f"✅ {file_info['path']} 文件读取成功")
|
|
166
421
|
else:
|
|
167
422
|
all_outputs.append(f"❌ {file_info['path']}: {result['stderr']}")
|
|
423
|
+
status_lines.append(f"❌ {file_info['path']} 文件读取失败")
|
|
168
424
|
overall_success = False
|
|
169
425
|
|
|
426
|
+
stdout_text = "\n".join(all_outputs)
|
|
427
|
+
# 仅打印每个文件的读取状态,不打印具体内容
|
|
428
|
+
try:
|
|
429
|
+
if status_lines:
|
|
430
|
+
print("\n".join(status_lines), end="\n")
|
|
431
|
+
except Exception:
|
|
432
|
+
pass
|
|
170
433
|
return {
|
|
171
434
|
"success": overall_success,
|
|
172
|
-
"stdout":
|
|
435
|
+
"stdout": stdout_text,
|
|
173
436
|
"stderr": "",
|
|
174
437
|
}
|
|
175
438
|
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
按需读取 symbols.jsonl 的工具。
|
|
4
|
+
|
|
5
|
+
用途:
|
|
6
|
+
- 避免Agent直接完整读取体积较大的符号表文件;
|
|
7
|
+
- 通过提供符号表路径与符号名称列表,仅返回匹配的符号记录。
|
|
8
|
+
|
|
9
|
+
参数:
|
|
10
|
+
- symbols_file (str): 符号表文件路径(.jsonl),或项目根目录/包含 .jarvis/c2rust 的目录
|
|
11
|
+
- symbols (List[str]): 需要读取的符号名称列表(支持 name 与 qualified_name 匹配)
|
|
12
|
+
|
|
13
|
+
返回:
|
|
14
|
+
- success (bool)
|
|
15
|
+
- stdout (str): JSON文本,包含查询结果
|
|
16
|
+
- stderr (str)
|
|
17
|
+
"""
|
|
18
|
+
import json
|
|
19
|
+
import os
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
from typing import Any, Dict, List
|
|
22
|
+
|
|
23
|
+
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ReadSymbolsTool:
|
|
27
|
+
# 文件名必须与工具名一致,便于注册表自动加载
|
|
28
|
+
name = "read_symbols"
|
|
29
|
+
description = "从symbols.jsonl按需读取指定符号的记录,避免完整加载大文件。参数包含符号表路径和符号名列表(匹配 name 与 qualified_name)(C2Rust工具专用)"
|
|
30
|
+
parameters = {
|
|
31
|
+
"type": "object",
|
|
32
|
+
"properties": {
|
|
33
|
+
"symbols_file": {
|
|
34
|
+
"type": "string",
|
|
35
|
+
"description": "符号表文件路径(.jsonl)。若为目录,则解析为 <dir>/.jarvis/c2rust/symbols.jsonl",
|
|
36
|
+
},
|
|
37
|
+
"symbols": {
|
|
38
|
+
"type": "array",
|
|
39
|
+
"items": {"type": "string"},
|
|
40
|
+
"description": "要检索的符号名称列表(支持 name 或 qualified_name 完全匹配)",
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
"required": ["symbols_file", "symbols"],
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
@staticmethod
|
|
47
|
+
def check() -> bool:
|
|
48
|
+
"""
|
|
49
|
+
检查工具是否可用。
|
|
50
|
+
仅在 c2rust 环境中启用(通过环境变量 JARVIS_C2RUST_ENABLED 判断)。
|
|
51
|
+
"""
|
|
52
|
+
return os.environ.get("JARVIS_C2RUST_ENABLED") == "1"
|
|
53
|
+
|
|
54
|
+
@staticmethod
|
|
55
|
+
def _resolve_symbols_jsonl_path(path_hint: str) -> Path:
|
|
56
|
+
"""
|
|
57
|
+
解析符号表路径:
|
|
58
|
+
- 若为目录,返回 <dir>/.jarvis/c2rust/symbols.jsonl
|
|
59
|
+
- 若为文件,直接返回
|
|
60
|
+
"""
|
|
61
|
+
p = Path(os.path.abspath(os.path.expanduser(path_hint)))
|
|
62
|
+
if p.is_dir():
|
|
63
|
+
candidate = p / ".jarvis" / "c2rust" / "symbols.jsonl"
|
|
64
|
+
return candidate
|
|
65
|
+
return p
|
|
66
|
+
|
|
67
|
+
def execute(self, args: Dict[str, Any]) -> Dict[str, Any]:
|
|
68
|
+
try:
|
|
69
|
+
symbols_file_arg = args.get("symbols_file")
|
|
70
|
+
symbols_arg = args.get("symbols")
|
|
71
|
+
|
|
72
|
+
if not isinstance(symbols_file_arg, str) or not symbols_file_arg.strip():
|
|
73
|
+
return {"success": False, "stdout": "", "stderr": "缺少或无效的 symbols_file 参数"}
|
|
74
|
+
|
|
75
|
+
if not isinstance(symbols_arg, list) or not all(isinstance(s, str) for s in symbols_arg):
|
|
76
|
+
return {"success": False, "stdout": "", "stderr": "symbols 参数必须是字符串列表"}
|
|
77
|
+
|
|
78
|
+
symbols_path = self._resolve_symbols_jsonl_path(symbols_file_arg)
|
|
79
|
+
print(f"[read_symbols] Resolved symbols file path: {symbols_path}")
|
|
80
|
+
if not symbols_path.exists():
|
|
81
|
+
return {"success": False, "stdout": "", "stderr": f"符号表文件不存在: {symbols_path}"}
|
|
82
|
+
if not symbols_path.is_file():
|
|
83
|
+
return {"success": False, "stdout": "", "stderr": f"符号表路径不是文件: {symbols_path}"}
|
|
84
|
+
|
|
85
|
+
# 使用集合提升匹配效率;保持原请求顺序以便输出
|
|
86
|
+
requested: List[str] = [s.strip() for s in symbols_arg if s and s.strip()]
|
|
87
|
+
wanted_set = set(requested)
|
|
88
|
+
print(f"[read_symbols] Requested {len(wanted_set)} unique symbols.")
|
|
89
|
+
|
|
90
|
+
results: Dict[str, List[Dict[str, Any]]] = {s: [] for s in requested}
|
|
91
|
+
|
|
92
|
+
# 流式读取,避免载入整个大文件
|
|
93
|
+
with open(symbols_path, "r", encoding="utf-8") as f:
|
|
94
|
+
for line in f:
|
|
95
|
+
line = line.strip()
|
|
96
|
+
if not line:
|
|
97
|
+
continue
|
|
98
|
+
try:
|
|
99
|
+
obj = json.loads(line)
|
|
100
|
+
except Exception:
|
|
101
|
+
continue
|
|
102
|
+
|
|
103
|
+
name = obj.get("name") or ""
|
|
104
|
+
qname = obj.get("qualified_name") or ""
|
|
105
|
+
|
|
106
|
+
# 仅当命中请求的符号时才记录
|
|
107
|
+
if name in wanted_set:
|
|
108
|
+
results[name].append(obj)
|
|
109
|
+
if qname in wanted_set and qname != name:
|
|
110
|
+
results[qname].append(obj)
|
|
111
|
+
|
|
112
|
+
not_found = [s for s in requested if not results.get(s)]
|
|
113
|
+
if not_found:
|
|
114
|
+
print(f"[read_symbols] Symbols not found: {not_found}")
|
|
115
|
+
found_counts = {s: len(results.get(s, [])) for s in requested}
|
|
116
|
+
|
|
117
|
+
out_obj: Dict[str, Any] = {
|
|
118
|
+
"symbols_file": str(symbols_path),
|
|
119
|
+
"requested": requested,
|
|
120
|
+
"found_counts": found_counts,
|
|
121
|
+
"not_found": not_found,
|
|
122
|
+
"items": results,
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
stdout = json.dumps(out_obj, ensure_ascii=False, indent=2)
|
|
126
|
+
# 简要状态打印(不包含具体内容)
|
|
127
|
+
try:
|
|
128
|
+
status_lines = []
|
|
129
|
+
for s in requested:
|
|
130
|
+
cnt = found_counts.get(s, 0)
|
|
131
|
+
status_lines.append(f"[read_symbols] {s}: {cnt} 条匹配")
|
|
132
|
+
if status_lines:
|
|
133
|
+
print("\n".join(status_lines), end="\n")
|
|
134
|
+
except Exception:
|
|
135
|
+
pass
|
|
136
|
+
|
|
137
|
+
return {"success": True, "stdout": stdout, "stderr": ""}
|
|
138
|
+
|
|
139
|
+
except Exception as e:
|
|
140
|
+
PrettyOutput.print(str(e), OutputType.ERROR)
|
|
141
|
+
return {"success": False, "stdout": "", "stderr": f"读取符号表失败: {str(e)}"}
|
|
@@ -9,7 +9,7 @@ from jarvis.jarvis_utils.config import (
|
|
|
9
9
|
)
|
|
10
10
|
from jarvis.jarvis_utils.http import get as http_get
|
|
11
11
|
from markdownify import markdownify as md # type: ignore
|
|
12
|
-
import requests
|
|
12
|
+
import requests # type: ignore[import-untyped]
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
class WebpageTool:
|
|
@@ -58,7 +58,8 @@ class WebpageTool:
|
|
|
58
58
|
2. 包含网页标题
|
|
59
59
|
3. 根据用户需求提供准确、完整的信息"""
|
|
60
60
|
response = model.chat_until_success(prompt) # type: ignore
|
|
61
|
-
|
|
61
|
+
if response and response.strip():
|
|
62
|
+
return {"success": True, "stdout": response, "stderr": ""}
|
|
62
63
|
|
|
63
64
|
# 2) 然后尝试使用默认平台(normal)的 web 能力
|
|
64
65
|
model = PlatformRegistry().get_normal_platform()
|
|
@@ -73,7 +74,8 @@ class WebpageTool:
|
|
|
73
74
|
2. 包含网页标题
|
|
74
75
|
3. 根据用户需求提供准确、完整的信息"""
|
|
75
76
|
response = model.chat_until_success(prompt) # type: ignore
|
|
76
|
-
|
|
77
|
+
if response and response.strip():
|
|
78
|
+
return {"success": True, "stdout": response, "stderr": ""}
|
|
77
79
|
|
|
78
80
|
# 3) 回退:使用 requests 抓取网页,再用模型分析
|
|
79
81
|
|