jarvis-ai-assistant 0.1.126__py3-none-any.whl → 0.1.129__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 +108 -95
- jarvis/jarvis_agent/main.py +77 -0
- jarvis/jarvis_code_agent/builtin_input_handler.py +43 -0
- jarvis/jarvis_code_agent/code_agent.py +17 -81
- jarvis/jarvis_code_agent/file_input_handler.py +88 -0
- jarvis/jarvis_code_agent/patch.py +142 -114
- jarvis/jarvis_code_agent/shell_input_handler.py +8 -2
- jarvis/jarvis_codebase/main.py +240 -213
- jarvis/jarvis_dev/main.py +4 -3
- jarvis/jarvis_multi_agent/__init__.py +51 -40
- jarvis/jarvis_platform/base.py +6 -5
- jarvis/jarvis_platform_manager/main.py +1 -1
- jarvis/jarvis_rag/main.py +250 -186
- jarvis/jarvis_smart_shell/main.py +0 -1
- jarvis/jarvis_tools/ask_codebase.py +4 -3
- jarvis/jarvis_tools/chdir.py +22 -22
- jarvis/jarvis_tools/code_review.py +38 -33
- jarvis/jarvis_tools/execute_shell.py +0 -3
- jarvis/jarvis_tools/file_operation.py +56 -55
- jarvis/jarvis_tools/git_commiter.py +60 -50
- jarvis/jarvis_tools/read_code.py +143 -0
- jarvis/jarvis_tools/read_webpage.py +50 -30
- jarvis/jarvis_tools/registry.py +4 -21
- jarvis/jarvis_tools/search_web.py +61 -36
- jarvis/jarvis_tools/tool_generator.py +78 -36
- jarvis/jarvis_utils/__init__.py +17 -17
- jarvis/jarvis_utils/config.py +87 -51
- jarvis/jarvis_utils/embedding.py +49 -48
- jarvis/jarvis_utils/git_utils.py +34 -34
- jarvis/jarvis_utils/globals.py +26 -26
- jarvis/jarvis_utils/input.py +61 -45
- jarvis/jarvis_utils/methodology.py +94 -76
- jarvis/jarvis_utils/output.py +63 -62
- jarvis/jarvis_utils/utils.py +2 -2
- {jarvis_ai_assistant-0.1.126.dist-info → jarvis_ai_assistant-0.1.129.dist-info}/METADATA +1 -1
- jarvis_ai_assistant-0.1.129.dist-info/RECORD +78 -0
- {jarvis_ai_assistant-0.1.126.dist-info → jarvis_ai_assistant-0.1.129.dist-info}/entry_points.txt +2 -0
- jarvis_ai_assistant-0.1.126.dist-info/RECORD +0 -74
- {jarvis_ai_assistant-0.1.126.dist-info → jarvis_ai_assistant-0.1.129.dist-info}/LICENSE +0 -0
- {jarvis_ai_assistant-0.1.126.dist-info → jarvis_ai_assistant-0.1.129.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.1.126.dist-info → jarvis_ai_assistant-0.1.129.dist-info}/top_level.txt +0 -0
jarvis/__init__.py
CHANGED
jarvis/jarvis_agent/__init__.py
CHANGED
|
@@ -3,6 +3,7 @@ from typing import Any, Callable, List, Optional, Tuple, Union
|
|
|
3
3
|
|
|
4
4
|
from prompt_toolkit import prompt
|
|
5
5
|
import yaml
|
|
6
|
+
from yaspin import yaspin
|
|
6
7
|
|
|
7
8
|
from jarvis.jarvis_agent.output_handler import OutputHandler
|
|
8
9
|
from jarvis.jarvis_platform.base import BasePlatform
|
|
@@ -48,7 +49,8 @@ class Agent:
|
|
|
48
49
|
record_methodology: Optional[bool] = None,
|
|
49
50
|
need_summary: Optional[bool] = None,
|
|
50
51
|
max_context_length: Optional[int] = None,
|
|
51
|
-
execute_tool_confirm: Optional[bool] = None
|
|
52
|
+
execute_tool_confirm: Optional[bool] = None,
|
|
53
|
+
multiline_inputer: Optional[Callable[[str], str]] = None):
|
|
52
54
|
self.name = make_agent_name(name)
|
|
53
55
|
self.description = description
|
|
54
56
|
# 初始化平台和模型
|
|
@@ -66,9 +68,10 @@ class Agent:
|
|
|
66
68
|
if model_name is not None:
|
|
67
69
|
self.model.set_model_name(model_name)
|
|
68
70
|
|
|
71
|
+
self.model.set_suppress_output(False)
|
|
69
72
|
|
|
70
|
-
self.output_handler = output_handler
|
|
71
|
-
|
|
73
|
+
self.output_handler = output_handler if output_handler else [ToolRegistry()]
|
|
74
|
+
self.multiline_inputer = multiline_inputer if multiline_inputer else get_multiline_input
|
|
72
75
|
|
|
73
76
|
self.record_methodology = record_methodology if record_methodology is not None else is_record_methodology()
|
|
74
77
|
self.use_methodology = use_methodology if use_methodology is not None else is_use_methodology()
|
|
@@ -165,6 +168,7 @@ class Agent:
|
|
|
165
168
|
message, need_return = handler(message, self)
|
|
166
169
|
if need_return:
|
|
167
170
|
return message
|
|
171
|
+
PrettyOutput.section("模型输出", OutputType.SYSTEM)
|
|
168
172
|
return self.model.chat_until_success(message) # type: ignore
|
|
169
173
|
|
|
170
174
|
|
|
@@ -184,35 +188,38 @@ class Agent:
|
|
|
184
188
|
"""
|
|
185
189
|
# Create a new model instance to summarize, avoid affecting the main conversation
|
|
186
190
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
prompt = """请总结之前对话中的关键信息,包括:
|
|
190
|
-
1. 当前任务目标
|
|
191
|
-
2. 已确认的关键信息
|
|
192
|
-
3. 已尝试的解决方案
|
|
193
|
-
4. 当前进展
|
|
194
|
-
5. 待解决的问题
|
|
195
|
-
|
|
196
|
-
请用简洁的要点形式描述,突出重要信息。不要包含对话细节。
|
|
197
|
-
"""
|
|
198
|
-
|
|
199
|
-
try:
|
|
200
|
-
summary = self._call_model(self.prompt + "\n" + prompt)
|
|
191
|
+
with yaspin(text="正在总结对话历史...", color="cyan") as spinner:
|
|
201
192
|
|
|
202
|
-
|
|
203
|
-
|
|
193
|
+
prompt = """请总结之前对话中的关键信息,包括:
|
|
194
|
+
1. 当前任务目标
|
|
195
|
+
2. 已确认的关键信息
|
|
196
|
+
3. 已尝试的解决方案
|
|
197
|
+
4. 当前进展
|
|
198
|
+
5. 待解决的问题
|
|
199
|
+
|
|
200
|
+
请用简洁的要点形式描述,突出重要信息。不要包含对话细节。
|
|
201
|
+
"""
|
|
204
202
|
|
|
205
|
-
|
|
206
|
-
|
|
203
|
+
try:
|
|
204
|
+
with spinner.hidden():
|
|
205
|
+
summary = self._call_model(self.prompt + "\n" + prompt)
|
|
206
|
+
|
|
207
|
+
# 清空当前对话历史,但保留系统消息
|
|
208
|
+
self.conversation_length = 0 # Reset conversation length
|
|
209
|
+
|
|
210
|
+
# 添加总结作为新的上下文
|
|
211
|
+
self.prompt = f"""以下是之前对话的关键信息总结:
|
|
207
212
|
|
|
208
|
-
{summary}
|
|
213
|
+
{summary}
|
|
209
214
|
|
|
210
|
-
请基于以上信息继续完成任务。
|
|
211
|
-
"""
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
215
|
+
请基于以上信息继续完成任务。
|
|
216
|
+
"""
|
|
217
|
+
self.conversation_length = len(self.prompt) # 设置新的起始长度
|
|
218
|
+
spinner.text = "总结对话历史完成"
|
|
219
|
+
spinner.ok("✅")
|
|
220
|
+
except Exception as e:
|
|
221
|
+
spinner.text = "总结对话历史失败"
|
|
222
|
+
spinner.fail("❌")
|
|
216
223
|
|
|
217
224
|
def _call_tools(self, response: str) -> Tuple[bool, Any]:
|
|
218
225
|
tool_list = []
|
|
@@ -225,7 +232,12 @@ class Agent:
|
|
|
225
232
|
if len(tool_list) == 0:
|
|
226
233
|
return False, ""
|
|
227
234
|
if not self.execute_tool_confirm or user_confirm(f"需要执行{tool_list[0].name()}确认执行?", True):
|
|
228
|
-
|
|
235
|
+
with yaspin(text=f"正在执行{tool_list[0].name()}...", color="cyan") as spinner:
|
|
236
|
+
with spinner.hidden():
|
|
237
|
+
result = tool_list[0].handle(response)
|
|
238
|
+
spinner.text = f"{tool_list[0].name()}执行完成"
|
|
239
|
+
spinner.ok("✅")
|
|
240
|
+
return result
|
|
229
241
|
return False, ""
|
|
230
242
|
|
|
231
243
|
|
|
@@ -239,56 +251,56 @@ class Agent:
|
|
|
239
251
|
- For main agent: May generate methodology if enabled
|
|
240
252
|
- For sub-agent: May generate summary if enabled
|
|
241
253
|
"""
|
|
242
|
-
PrettyOutput.section("任务完成", OutputType.SUCCESS)
|
|
243
|
-
|
|
244
254
|
if not self.is_sub_agent:
|
|
245
255
|
if self.record_methodology:
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
256
|
+
with yaspin(text="正在生成方法论...", color="cyan") as spinner:
|
|
257
|
+
try:
|
|
258
|
+
|
|
259
|
+
# 让模型判断是否需要生成方法论
|
|
260
|
+
analysis_prompt = """当前任务已结束,请分析是否需要生成方法论。
|
|
261
|
+
如果你认为需要生成方法论,请先确定是创建新方法论还是更新现有方法论。如果是更新现有方法论,请使用'update',否则使用'add'。
|
|
262
|
+
如果你认为不需要方法论,请解释原因。
|
|
263
|
+
方法论应适用于通用场景,不要包含任务特定信息,如代码提交信息等。
|
|
264
|
+
方法论应包含:问题重述、最优解决方案、注意事项(如有),除此之外不要包含其他内容。
|
|
265
|
+
只输出方法论工具调用指令,或不生成方法论的解释。不要输出其他内容。
|
|
266
|
+
"""
|
|
267
|
+
self.prompt = analysis_prompt
|
|
268
|
+
with spinner.hidden():
|
|
269
|
+
response = self._call_model(self.prompt)
|
|
270
|
+
|
|
271
|
+
with spinner.hidden():
|
|
272
|
+
self._call_tools(response)
|
|
273
|
+
spinner.text = "方法论生成完成"
|
|
274
|
+
spinner.ok("✅")
|
|
275
|
+
except Exception as e:
|
|
276
|
+
spinner.text = "方法论生成失败"
|
|
277
|
+
spinner.fail("❌")
|
|
264
278
|
return "任务完成"
|
|
265
279
|
|
|
266
280
|
if self.need_summary:
|
|
267
|
-
|
|
268
|
-
|
|
281
|
+
with yaspin(text="正在生成总结...", color="cyan") as spinner:
|
|
282
|
+
self.prompt = self.summary_prompt
|
|
283
|
+
with spinner.hidden():
|
|
284
|
+
ret = self._call_model(self.prompt)
|
|
285
|
+
spinner.text = "总结生成完成"
|
|
286
|
+
spinner.ok("✅")
|
|
287
|
+
return ret
|
|
269
288
|
|
|
270
289
|
return "任务完成"
|
|
271
290
|
|
|
272
291
|
|
|
273
|
-
def run(self, user_input: str
|
|
292
|
+
def run(self, user_input: str) -> Any:
|
|
274
293
|
"""Process user input and execute the task.
|
|
275
294
|
|
|
276
295
|
Args:
|
|
277
296
|
user_input: My task description or request
|
|
278
|
-
file_list: Optional list of files to process
|
|
279
297
|
|
|
280
298
|
Returns:
|
|
281
299
|
str|Dict: Task summary report or message to send
|
|
282
300
|
"""
|
|
283
301
|
try:
|
|
284
302
|
set_agent(self.name, self)
|
|
285
|
-
|
|
286
|
-
if file_list:
|
|
287
|
-
self.model.upload_files(file_list) # type: ignore
|
|
288
|
-
|
|
289
|
-
# 显示任务开始
|
|
290
|
-
PrettyOutput.section(f"开始新任务: {self.name}", OutputType.PLANNING)
|
|
291
|
-
|
|
303
|
+
|
|
292
304
|
self.prompt = f"{user_input}"
|
|
293
305
|
|
|
294
306
|
if self.first:
|
|
@@ -298,9 +310,6 @@ class Agent:
|
|
|
298
310
|
|
|
299
311
|
while True:
|
|
300
312
|
try:
|
|
301
|
-
# 显示思考状态
|
|
302
|
-
PrettyOutput.print("正在分析任务...", OutputType.PROGRESS)
|
|
303
|
-
|
|
304
313
|
# 累加对话长度
|
|
305
314
|
self.conversation_length += get_context_token_count(self.prompt)
|
|
306
315
|
|
|
@@ -325,7 +334,7 @@ class Agent:
|
|
|
325
334
|
return self._complete_task()
|
|
326
335
|
|
|
327
336
|
# 获取用户输入
|
|
328
|
-
user_input =
|
|
337
|
+
user_input = self.multiline_inputer(f"{self.name}: 请输入,或输入空行来结束当前任务:")
|
|
329
338
|
|
|
330
339
|
if user_input:
|
|
331
340
|
self.prompt = user_input
|
|
@@ -360,41 +369,46 @@ class Agent:
|
|
|
360
369
|
def _load_tasks() -> dict:
|
|
361
370
|
"""Load tasks from .jarvis files in user home and current directory."""
|
|
362
371
|
tasks = {}
|
|
363
|
-
|
|
372
|
+
|
|
364
373
|
# Check .jarvis/pre-command in user directory
|
|
365
374
|
user_jarvis = os.path.expanduser("~/.jarvis/pre-command")
|
|
366
375
|
if os.path.exists(user_jarvis):
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
376
|
+
with yaspin(text=f"从{user_jarvis}加载预定义任务...", color="cyan") as spinner:
|
|
377
|
+
try:
|
|
378
|
+
with open(user_jarvis, "r", encoding="utf-8") as f:
|
|
379
|
+
user_tasks = yaml.safe_load(f)
|
|
380
|
+
|
|
381
|
+
if isinstance(user_tasks, dict):
|
|
382
|
+
# Validate and add user directory tasks
|
|
383
|
+
for name, desc in user_tasks.items():
|
|
384
|
+
if desc: # Ensure description is not empty
|
|
385
|
+
tasks[str(name)] = str(desc)
|
|
386
|
+
spinner.text = "预定义任务加载完成"
|
|
387
|
+
spinner.ok("✅")
|
|
388
|
+
except Exception as e:
|
|
389
|
+
spinner.text = "预定义任务加载失败"
|
|
390
|
+
spinner.fail("❌")
|
|
391
|
+
|
|
381
392
|
# Check .jarvis/pre-command in current directory
|
|
382
393
|
if os.path.exists(".jarvis/pre-command"):
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
394
|
+
with yaspin(text=f"从{os.path.abspath('.jarvis/pre-command')}加载预定义任务...", color="cyan") as spinner:
|
|
395
|
+
try:
|
|
396
|
+
with open(".jarvis/pre-command", "r", encoding="utf-8") as f:
|
|
397
|
+
local_tasks = yaml.safe_load(f)
|
|
398
|
+
|
|
399
|
+
if isinstance(local_tasks, dict):
|
|
400
|
+
# Validate and add current directory tasks, overwrite user directory tasks if there is a name conflict
|
|
401
|
+
for name, desc in local_tasks.items():
|
|
402
|
+
if desc: # Ensure description is not empty
|
|
403
|
+
tasks[str(name)] = str(desc)
|
|
404
|
+
spinner.text = "预定义任务加载完成"
|
|
405
|
+
spinner.ok("✅")
|
|
406
|
+
except Exception as e:
|
|
407
|
+
spinner.text = "预定义任务加载失败"
|
|
408
|
+
spinner.fail("❌")
|
|
396
409
|
|
|
397
410
|
return tasks
|
|
411
|
+
|
|
398
412
|
def _select_task(tasks: dict) -> str:
|
|
399
413
|
"""Let user select a task from the list or skip. Returns task description if selected."""
|
|
400
414
|
if not tasks:
|
|
@@ -533,7 +547,6 @@ def main():
|
|
|
533
547
|
# Add argument parser
|
|
534
548
|
init_env()
|
|
535
549
|
parser = argparse.ArgumentParser(description='Jarvis AI assistant')
|
|
536
|
-
parser.add_argument('-f', '--files', nargs='*', help='List of files to process')
|
|
537
550
|
parser.add_argument('-p', '--platform', type=str, help='Platform to use')
|
|
538
551
|
parser.add_argument('-m', '--model', type=str, help='Model to use')
|
|
539
552
|
args = parser.parse_args()
|
|
@@ -548,7 +561,7 @@ def main():
|
|
|
548
561
|
selected_task = _select_task(tasks)
|
|
549
562
|
if selected_task:
|
|
550
563
|
PrettyOutput.print(f"执行任务: {selected_task}", OutputType.INFO)
|
|
551
|
-
agent.run(selected_task
|
|
564
|
+
agent.run(selected_task)
|
|
552
565
|
return 0
|
|
553
566
|
|
|
554
567
|
# 如果没有选择预定义任务,进入交互模式
|
|
@@ -557,7 +570,7 @@ def main():
|
|
|
557
570
|
user_input = get_multiline_input("请输入你的任务(输入空行退出):")
|
|
558
571
|
if not user_input:
|
|
559
572
|
break
|
|
560
|
-
agent.run(user_input
|
|
573
|
+
agent.run(user_input)
|
|
561
574
|
except Exception as e:
|
|
562
575
|
PrettyOutput.print(f"错误: {str(e)}", OutputType.ERROR)
|
|
563
576
|
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import yaml
|
|
3
|
+
import os
|
|
4
|
+
from typing import Optional, List
|
|
5
|
+
from jarvis.jarvis_agent import Agent
|
|
6
|
+
from jarvis.jarvis_utils.input import get_multiline_input
|
|
7
|
+
from jarvis.jarvis_utils.output import PrettyOutput, OutputType
|
|
8
|
+
from jarvis.jarvis_utils.utils import init_env
|
|
9
|
+
|
|
10
|
+
# 从__init__.py导入系统提示
|
|
11
|
+
from jarvis.jarvis_agent import origin_agent_system_prompt
|
|
12
|
+
|
|
13
|
+
def load_config(config_path: str) -> dict:
|
|
14
|
+
"""Load configuration from YAML file
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
config_path: Path to the YAML configuration file
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
dict: Configuration dictionary
|
|
21
|
+
"""
|
|
22
|
+
if not os.path.exists(config_path):
|
|
23
|
+
PrettyOutput.print(f"配置文件 {config_path} 不存在,使用默认配置", OutputType.WARNING)
|
|
24
|
+
return {}
|
|
25
|
+
|
|
26
|
+
with open(config_path, 'r', encoding='utf-8') as f:
|
|
27
|
+
try:
|
|
28
|
+
config = yaml.safe_load(f)
|
|
29
|
+
return config if config else {}
|
|
30
|
+
except yaml.YAMLError as e:
|
|
31
|
+
PrettyOutput.print(f"配置文件解析失败: {str(e)}", OutputType.ERROR)
|
|
32
|
+
return {}
|
|
33
|
+
|
|
34
|
+
def main():
|
|
35
|
+
"""Main entry point for Jarvis agent"""
|
|
36
|
+
# Initialize environment
|
|
37
|
+
init_env()
|
|
38
|
+
|
|
39
|
+
# Set up argument parser
|
|
40
|
+
parser = argparse.ArgumentParser(description='Jarvis AI assistant')
|
|
41
|
+
parser.add_argument('-c', '--config', type=str, required=True,
|
|
42
|
+
help='Path to the YAML configuration file')
|
|
43
|
+
parser.add_argument('-t', '--task', type=str,
|
|
44
|
+
help='Initial task to execute')
|
|
45
|
+
args = parser.parse_args()
|
|
46
|
+
|
|
47
|
+
# Load configuration
|
|
48
|
+
config = load_config(args.config)
|
|
49
|
+
|
|
50
|
+
# Create and run agent
|
|
51
|
+
try:
|
|
52
|
+
agent = Agent(**config)
|
|
53
|
+
|
|
54
|
+
# Run agent with initial task if specified
|
|
55
|
+
if args.task:
|
|
56
|
+
PrettyOutput.print(f"执行初始任务: {args.task}", OutputType.INFO)
|
|
57
|
+
agent.run(args.task)
|
|
58
|
+
return 0
|
|
59
|
+
|
|
60
|
+
# Enter interactive mode if no initial task
|
|
61
|
+
while True:
|
|
62
|
+
try:
|
|
63
|
+
user_input = get_multiline_input("请输入你的任务(输入空行退出):")
|
|
64
|
+
if not user_input:
|
|
65
|
+
break
|
|
66
|
+
agent.run(user_input)
|
|
67
|
+
except Exception as e:
|
|
68
|
+
PrettyOutput.print(f"错误: {str(e)}", OutputType.ERROR)
|
|
69
|
+
|
|
70
|
+
except Exception as e:
|
|
71
|
+
PrettyOutput.print(f"初始化错误: {str(e)}", OutputType.ERROR)
|
|
72
|
+
return 1
|
|
73
|
+
|
|
74
|
+
return 0
|
|
75
|
+
|
|
76
|
+
if __name__ == "__main__":
|
|
77
|
+
exit(main())
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from typing import Any, Tuple
|
|
3
|
+
|
|
4
|
+
from jarvis.jarvis_utils.output import PrettyOutput, OutputType
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def builtin_input_handler(user_input: str, agent: Any) -> Tuple[str, bool]:
|
|
8
|
+
"""
|
|
9
|
+
处理内置的特殊输入标记,并追加相应的提示词
|
|
10
|
+
|
|
11
|
+
参数:
|
|
12
|
+
user_input: 用户输入
|
|
13
|
+
agent: 代理对象
|
|
14
|
+
|
|
15
|
+
返回:
|
|
16
|
+
Tuple[str, bool]: 处理后的输入和是否需要进一步处理
|
|
17
|
+
"""
|
|
18
|
+
# 查找特殊标记
|
|
19
|
+
special_tags = re.findall(r"'<([^>]+)>'", user_input)
|
|
20
|
+
|
|
21
|
+
if not special_tags:
|
|
22
|
+
return user_input, False
|
|
23
|
+
|
|
24
|
+
# 使用集合去重
|
|
25
|
+
processed_tags = set()
|
|
26
|
+
# 处理每个标记
|
|
27
|
+
for tag in special_tags:
|
|
28
|
+
if tag in processed_tags:
|
|
29
|
+
continue
|
|
30
|
+
processed_tags.add(tag)
|
|
31
|
+
|
|
32
|
+
if tag == "CodeBase":
|
|
33
|
+
user_input = user_input.replace(f"'<{tag}>'", "")
|
|
34
|
+
user_input += "\n请使用ask_codebase工具查询代码库"
|
|
35
|
+
elif tag == "Web":
|
|
36
|
+
user_input = user_input.replace(f"'<{tag}>'", "")
|
|
37
|
+
user_input += "\n请使用search_web工具进行网页搜索"
|
|
38
|
+
elif tag == "RAG":
|
|
39
|
+
user_input = user_input.replace(f"'<{tag}>'", "")
|
|
40
|
+
user_input += "\n请使用rag工具进行知识库检索"
|
|
41
|
+
# 移除对未知标记的警告输出
|
|
42
|
+
|
|
43
|
+
return user_input, False
|
|
@@ -3,7 +3,11 @@ import subprocess
|
|
|
3
3
|
import os
|
|
4
4
|
from typing import Any, Tuple
|
|
5
5
|
|
|
6
|
+
from yaspin import yaspin
|
|
7
|
+
|
|
6
8
|
from jarvis.jarvis_agent import Agent
|
|
9
|
+
from jarvis.jarvis_code_agent.builtin_input_handler import builtin_input_handler
|
|
10
|
+
from jarvis.jarvis_code_agent.file_input_handler import file_input_handler
|
|
7
11
|
from jarvis.jarvis_code_agent.shell_input_handler import shell_input_handler
|
|
8
12
|
from jarvis.jarvis_code_agent.patch import PatchOutputHandler
|
|
9
13
|
from jarvis.jarvis_platform.registry import PlatformRegistry
|
|
@@ -19,80 +23,6 @@ from jarvis.jarvis_utils.utils import init_env, user_confirm
|
|
|
19
23
|
|
|
20
24
|
|
|
21
25
|
|
|
22
|
-
def file_input_handler(user_input: str, agent: Any) -> Tuple[str, bool]:
|
|
23
|
-
prompt = user_input
|
|
24
|
-
files = []
|
|
25
|
-
|
|
26
|
-
file_refs = re.findall(r"'([^']+)'", user_input)
|
|
27
|
-
for ref in file_refs:
|
|
28
|
-
# Handle file:start,end or file:start:end format
|
|
29
|
-
if ':' in ref:
|
|
30
|
-
file_path, line_range = ref.split(':', 1)
|
|
31
|
-
# Initialize with default values
|
|
32
|
-
start_line = 1 # 1-based
|
|
33
|
-
end_line = -1
|
|
34
|
-
|
|
35
|
-
# Process line range if specified
|
|
36
|
-
if ',' in line_range or ':' in line_range:
|
|
37
|
-
try:
|
|
38
|
-
raw_start, raw_end = map(int, re.split(r'[,:]', line_range))
|
|
39
|
-
|
|
40
|
-
# Handle special values and Python-style negative indices
|
|
41
|
-
try:
|
|
42
|
-
with open(file_path, 'r', encoding='utf-8') as f:
|
|
43
|
-
total_lines = len(f.readlines())
|
|
44
|
-
except FileNotFoundError:
|
|
45
|
-
PrettyOutput.print(f"文件不存在: {file_path}", OutputType.WARNING)
|
|
46
|
-
continue
|
|
47
|
-
# Process start line
|
|
48
|
-
if raw_start == 0: # 0表示整个文件
|
|
49
|
-
start_line = 1
|
|
50
|
-
end_line = total_lines
|
|
51
|
-
else:
|
|
52
|
-
start_line = raw_start if raw_start > 0 else total_lines + raw_start + 1
|
|
53
|
-
|
|
54
|
-
# Process end line
|
|
55
|
-
if raw_end == 0: # 0表示整个文件(如果start也是0)
|
|
56
|
-
end_line = total_lines
|
|
57
|
-
else:
|
|
58
|
-
end_line = raw_end if raw_end > 0 else total_lines + raw_end + 1
|
|
59
|
-
|
|
60
|
-
# Auto-correct ranges
|
|
61
|
-
start_line = max(1, min(start_line, total_lines))
|
|
62
|
-
end_line = max(start_line, min(end_line, total_lines))
|
|
63
|
-
|
|
64
|
-
# Final validation
|
|
65
|
-
if start_line < 1 or end_line > total_lines or start_line > end_line:
|
|
66
|
-
raise ValueError
|
|
67
|
-
|
|
68
|
-
except:
|
|
69
|
-
continue
|
|
70
|
-
|
|
71
|
-
# Add file if it exists
|
|
72
|
-
if os.path.isfile(file_path):
|
|
73
|
-
files.append({
|
|
74
|
-
"path": file_path,
|
|
75
|
-
"start_line": start_line,
|
|
76
|
-
"end_line": end_line
|
|
77
|
-
})
|
|
78
|
-
else:
|
|
79
|
-
# Handle simple file path
|
|
80
|
-
if os.path.isfile(ref):
|
|
81
|
-
files.append({
|
|
82
|
-
"path": ref,
|
|
83
|
-
"start_line": 1, # 1-based
|
|
84
|
-
"end_line": -1
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
# Read and process files if any were found
|
|
88
|
-
if files:
|
|
89
|
-
result = FileOperationTool().execute({"operation":"read","files": files})
|
|
90
|
-
if result["success"]:
|
|
91
|
-
return result["stdout"] + "\n" + prompt, False
|
|
92
|
-
|
|
93
|
-
return prompt, False
|
|
94
|
-
|
|
95
|
-
|
|
96
26
|
|
|
97
27
|
class CodeAgent:
|
|
98
28
|
def __init__(self):
|
|
@@ -203,18 +133,23 @@ class CodeAgent:
|
|
|
203
133
|
output_handler=[tool_registry, PatchOutputHandler()],
|
|
204
134
|
platform=PlatformRegistry().get_codegen_platform(),
|
|
205
135
|
record_methodology=False,
|
|
206
|
-
input_handler=[shell_input_handler, file_input_handler],
|
|
136
|
+
input_handler=[shell_input_handler, file_input_handler, builtin_input_handler],
|
|
207
137
|
need_summary=False)
|
|
208
138
|
|
|
209
139
|
|
|
210
140
|
|
|
211
141
|
def _init_env(self):
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
142
|
+
with yaspin(text="正在初始化环境...", color="cyan") as spinner:
|
|
143
|
+
curr_dir = os.getcwd()
|
|
144
|
+
git_dir = find_git_root(curr_dir)
|
|
145
|
+
self.root_dir = git_dir
|
|
146
|
+
if has_uncommitted_changes():
|
|
147
|
+
with spinner.hidden():
|
|
148
|
+
git_commiter = GitCommitTool()
|
|
149
|
+
git_commiter.execute({})
|
|
150
|
+
else:
|
|
151
|
+
spinner.text = "环境初始化完成"
|
|
152
|
+
spinner.ok("✅")
|
|
218
153
|
|
|
219
154
|
|
|
220
155
|
|
|
@@ -229,6 +164,7 @@ class CodeAgent:
|
|
|
229
164
|
"""
|
|
230
165
|
try:
|
|
231
166
|
self._init_env()
|
|
167
|
+
|
|
232
168
|
start_commit = get_latest_commit_hash()
|
|
233
169
|
|
|
234
170
|
try:
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import re
|
|
5
|
+
from typing import Any, Tuple
|
|
6
|
+
|
|
7
|
+
from yaspin import yaspin
|
|
8
|
+
|
|
9
|
+
from jarvis.jarvis_tools.file_operation import FileOperationTool
|
|
10
|
+
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def file_input_handler(user_input: str, agent: Any) -> Tuple[str, bool]:
|
|
14
|
+
prompt = user_input
|
|
15
|
+
files = []
|
|
16
|
+
|
|
17
|
+
file_refs = re.findall(r"'([^']+)'", user_input)
|
|
18
|
+
for ref in file_refs:
|
|
19
|
+
# Handle file:start,end or file:start:end format
|
|
20
|
+
if ':' in ref:
|
|
21
|
+
file_path, line_range = ref.split(':', 1)
|
|
22
|
+
# Initialize with default values
|
|
23
|
+
start_line = 1 # 1-based
|
|
24
|
+
end_line = -1
|
|
25
|
+
|
|
26
|
+
# Process line range if specified
|
|
27
|
+
if ',' in line_range or ':' in line_range:
|
|
28
|
+
try:
|
|
29
|
+
raw_start, raw_end = map(int, re.split(r'[,:]', line_range))
|
|
30
|
+
|
|
31
|
+
# Handle special values and Python-style negative indices
|
|
32
|
+
try:
|
|
33
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
34
|
+
total_lines = len(f.readlines())
|
|
35
|
+
except FileNotFoundError:
|
|
36
|
+
PrettyOutput.print(f"文件不存在: {file_path}", OutputType.WARNING)
|
|
37
|
+
continue
|
|
38
|
+
# Process start line
|
|
39
|
+
if raw_start == 0: # 0表示整个文件
|
|
40
|
+
start_line = 1
|
|
41
|
+
end_line = total_lines
|
|
42
|
+
else:
|
|
43
|
+
start_line = raw_start if raw_start > 0 else total_lines + raw_start + 1
|
|
44
|
+
|
|
45
|
+
# Process end line
|
|
46
|
+
if raw_end == 0: # 0表示整个文件(如果start也是0)
|
|
47
|
+
end_line = total_lines
|
|
48
|
+
else:
|
|
49
|
+
end_line = raw_end if raw_end > 0 else total_lines + raw_end + 1
|
|
50
|
+
|
|
51
|
+
# Auto-correct ranges
|
|
52
|
+
start_line = max(1, min(start_line, total_lines))
|
|
53
|
+
end_line = max(start_line, min(end_line, total_lines))
|
|
54
|
+
|
|
55
|
+
# Final validation
|
|
56
|
+
if start_line < 1 or end_line > total_lines or start_line > end_line:
|
|
57
|
+
raise ValueError
|
|
58
|
+
|
|
59
|
+
except:
|
|
60
|
+
continue
|
|
61
|
+
|
|
62
|
+
# Add file if it exists
|
|
63
|
+
if os.path.isfile(file_path):
|
|
64
|
+
files.append({
|
|
65
|
+
"path": file_path,
|
|
66
|
+
"start_line": start_line,
|
|
67
|
+
"end_line": end_line
|
|
68
|
+
})
|
|
69
|
+
else:
|
|
70
|
+
# Handle simple file path
|
|
71
|
+
if os.path.isfile(ref):
|
|
72
|
+
files.append({
|
|
73
|
+
"path": ref,
|
|
74
|
+
"start_line": 1, # 1-based
|
|
75
|
+
"end_line": -1
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
# Read and process files if any were found
|
|
79
|
+
if files:
|
|
80
|
+
with yaspin(text="正在读取文件...", color="cyan") as spinner:
|
|
81
|
+
result = FileOperationTool().execute({"operation":"read","files": files})
|
|
82
|
+
if result["success"]:
|
|
83
|
+
spinner.text = "文件读取完成"
|
|
84
|
+
spinner.ok("✅")
|
|
85
|
+
return result["stdout"] + "\n" + prompt, False
|
|
86
|
+
|
|
87
|
+
return prompt, False
|
|
88
|
+
|