jarvis-ai-assistant 0.1.222__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.
Files changed (162) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/jarvis_agent/__init__.py +1143 -245
  3. jarvis/jarvis_agent/agent_manager.py +97 -0
  4. jarvis/jarvis_agent/builtin_input_handler.py +12 -10
  5. jarvis/jarvis_agent/config_editor.py +57 -0
  6. jarvis/jarvis_agent/edit_file_handler.py +392 -99
  7. jarvis/jarvis_agent/event_bus.py +48 -0
  8. jarvis/jarvis_agent/events.py +157 -0
  9. jarvis/jarvis_agent/file_context_handler.py +79 -0
  10. jarvis/jarvis_agent/file_methodology_manager.py +117 -0
  11. jarvis/jarvis_agent/jarvis.py +1117 -147
  12. jarvis/jarvis_agent/main.py +78 -34
  13. jarvis/jarvis_agent/memory_manager.py +195 -0
  14. jarvis/jarvis_agent/methodology_share_manager.py +174 -0
  15. jarvis/jarvis_agent/prompt_manager.py +82 -0
  16. jarvis/jarvis_agent/prompts.py +46 -9
  17. jarvis/jarvis_agent/protocols.py +4 -1
  18. jarvis/jarvis_agent/rewrite_file_handler.py +141 -0
  19. jarvis/jarvis_agent/run_loop.py +146 -0
  20. jarvis/jarvis_agent/session_manager.py +9 -9
  21. jarvis/jarvis_agent/share_manager.py +228 -0
  22. jarvis/jarvis_agent/shell_input_handler.py +23 -3
  23. jarvis/jarvis_agent/stdio_redirect.py +295 -0
  24. jarvis/jarvis_agent/task_analyzer.py +212 -0
  25. jarvis/jarvis_agent/task_manager.py +154 -0
  26. jarvis/jarvis_agent/task_planner.py +496 -0
  27. jarvis/jarvis_agent/tool_executor.py +8 -4
  28. jarvis/jarvis_agent/tool_share_manager.py +139 -0
  29. jarvis/jarvis_agent/user_interaction.py +42 -0
  30. jarvis/jarvis_agent/utils.py +54 -0
  31. jarvis/jarvis_agent/web_bridge.py +189 -0
  32. jarvis/jarvis_agent/web_output_sink.py +53 -0
  33. jarvis/jarvis_agent/web_server.py +751 -0
  34. jarvis/jarvis_c2rust/__init__.py +26 -0
  35. jarvis/jarvis_c2rust/cli.py +613 -0
  36. jarvis/jarvis_c2rust/collector.py +258 -0
  37. jarvis/jarvis_c2rust/library_replacer.py +1122 -0
  38. jarvis/jarvis_c2rust/llm_module_agent.py +1300 -0
  39. jarvis/jarvis_c2rust/optimizer.py +960 -0
  40. jarvis/jarvis_c2rust/scanner.py +1681 -0
  41. jarvis/jarvis_c2rust/transpiler.py +2325 -0
  42. jarvis/jarvis_code_agent/build_validation_config.py +133 -0
  43. jarvis/jarvis_code_agent/code_agent.py +1605 -178
  44. jarvis/jarvis_code_agent/code_analyzer/__init__.py +62 -0
  45. jarvis/jarvis_code_agent/code_analyzer/base_language.py +74 -0
  46. jarvis/jarvis_code_agent/code_analyzer/build_validator/__init__.py +44 -0
  47. jarvis/jarvis_code_agent/code_analyzer/build_validator/base.py +102 -0
  48. jarvis/jarvis_code_agent/code_analyzer/build_validator/cmake.py +59 -0
  49. jarvis/jarvis_code_agent/code_analyzer/build_validator/detector.py +125 -0
  50. jarvis/jarvis_code_agent/code_analyzer/build_validator/fallback.py +69 -0
  51. jarvis/jarvis_code_agent/code_analyzer/build_validator/go.py +38 -0
  52. jarvis/jarvis_code_agent/code_analyzer/build_validator/java_gradle.py +44 -0
  53. jarvis/jarvis_code_agent/code_analyzer/build_validator/java_maven.py +38 -0
  54. jarvis/jarvis_code_agent/code_analyzer/build_validator/makefile.py +50 -0
  55. jarvis/jarvis_code_agent/code_analyzer/build_validator/nodejs.py +93 -0
  56. jarvis/jarvis_code_agent/code_analyzer/build_validator/python.py +129 -0
  57. jarvis/jarvis_code_agent/code_analyzer/build_validator/rust.py +54 -0
  58. jarvis/jarvis_code_agent/code_analyzer/build_validator/validator.py +154 -0
  59. jarvis/jarvis_code_agent/code_analyzer/build_validator.py +43 -0
  60. jarvis/jarvis_code_agent/code_analyzer/context_manager.py +363 -0
  61. jarvis/jarvis_code_agent/code_analyzer/context_recommender.py +18 -0
  62. jarvis/jarvis_code_agent/code_analyzer/dependency_analyzer.py +132 -0
  63. jarvis/jarvis_code_agent/code_analyzer/file_ignore.py +330 -0
  64. jarvis/jarvis_code_agent/code_analyzer/impact_analyzer.py +781 -0
  65. jarvis/jarvis_code_agent/code_analyzer/language_registry.py +185 -0
  66. jarvis/jarvis_code_agent/code_analyzer/language_support.py +89 -0
  67. jarvis/jarvis_code_agent/code_analyzer/languages/__init__.py +31 -0
  68. jarvis/jarvis_code_agent/code_analyzer/languages/c_cpp_language.py +231 -0
  69. jarvis/jarvis_code_agent/code_analyzer/languages/go_language.py +183 -0
  70. jarvis/jarvis_code_agent/code_analyzer/languages/python_language.py +219 -0
  71. jarvis/jarvis_code_agent/code_analyzer/languages/rust_language.py +209 -0
  72. jarvis/jarvis_code_agent/code_analyzer/llm_context_recommender.py +451 -0
  73. jarvis/jarvis_code_agent/code_analyzer/symbol_extractor.py +77 -0
  74. jarvis/jarvis_code_agent/code_analyzer/tree_sitter_extractor.py +48 -0
  75. jarvis/jarvis_code_agent/lint.py +275 -13
  76. jarvis/jarvis_code_agent/utils.py +142 -0
  77. jarvis/jarvis_code_analysis/checklists/loader.py +20 -6
  78. jarvis/jarvis_code_analysis/code_review.py +583 -548
  79. jarvis/jarvis_data/config_schema.json +339 -28
  80. jarvis/jarvis_git_squash/main.py +22 -13
  81. jarvis/jarvis_git_utils/git_commiter.py +171 -55
  82. jarvis/jarvis_mcp/sse_mcp_client.py +22 -15
  83. jarvis/jarvis_mcp/stdio_mcp_client.py +4 -4
  84. jarvis/jarvis_mcp/streamable_mcp_client.py +36 -16
  85. jarvis/jarvis_memory_organizer/memory_organizer.py +753 -0
  86. jarvis/jarvis_methodology/main.py +48 -63
  87. jarvis/jarvis_multi_agent/__init__.py +302 -43
  88. jarvis/jarvis_multi_agent/main.py +70 -24
  89. jarvis/jarvis_platform/ai8.py +40 -23
  90. jarvis/jarvis_platform/base.py +210 -49
  91. jarvis/jarvis_platform/human.py +11 -1
  92. jarvis/jarvis_platform/kimi.py +82 -76
  93. jarvis/jarvis_platform/openai.py +73 -1
  94. jarvis/jarvis_platform/registry.py +8 -15
  95. jarvis/jarvis_platform/tongyi.py +115 -101
  96. jarvis/jarvis_platform/yuanbao.py +89 -63
  97. jarvis/jarvis_platform_manager/main.py +194 -132
  98. jarvis/jarvis_platform_manager/service.py +122 -86
  99. jarvis/jarvis_rag/cli.py +156 -53
  100. jarvis/jarvis_rag/embedding_manager.py +155 -12
  101. jarvis/jarvis_rag/llm_interface.py +10 -13
  102. jarvis/jarvis_rag/query_rewriter.py +63 -12
  103. jarvis/jarvis_rag/rag_pipeline.py +222 -40
  104. jarvis/jarvis_rag/reranker.py +26 -3
  105. jarvis/jarvis_rag/retriever.py +270 -14
  106. jarvis/jarvis_sec/__init__.py +3605 -0
  107. jarvis/jarvis_sec/checkers/__init__.py +32 -0
  108. jarvis/jarvis_sec/checkers/c_checker.py +2680 -0
  109. jarvis/jarvis_sec/checkers/rust_checker.py +1108 -0
  110. jarvis/jarvis_sec/cli.py +116 -0
  111. jarvis/jarvis_sec/report.py +257 -0
  112. jarvis/jarvis_sec/status.py +264 -0
  113. jarvis/jarvis_sec/types.py +20 -0
  114. jarvis/jarvis_sec/workflow.py +219 -0
  115. jarvis/jarvis_smart_shell/main.py +405 -137
  116. jarvis/jarvis_stats/__init__.py +13 -0
  117. jarvis/jarvis_stats/cli.py +387 -0
  118. jarvis/jarvis_stats/stats.py +711 -0
  119. jarvis/jarvis_stats/storage.py +612 -0
  120. jarvis/jarvis_stats/visualizer.py +282 -0
  121. jarvis/jarvis_tools/ask_user.py +1 -0
  122. jarvis/jarvis_tools/base.py +18 -2
  123. jarvis/jarvis_tools/clear_memory.py +239 -0
  124. jarvis/jarvis_tools/cli/main.py +220 -144
  125. jarvis/jarvis_tools/execute_script.py +52 -12
  126. jarvis/jarvis_tools/file_analyzer.py +17 -12
  127. jarvis/jarvis_tools/generate_new_tool.py +46 -24
  128. jarvis/jarvis_tools/read_code.py +277 -18
  129. jarvis/jarvis_tools/read_symbols.py +141 -0
  130. jarvis/jarvis_tools/read_webpage.py +86 -13
  131. jarvis/jarvis_tools/registry.py +294 -90
  132. jarvis/jarvis_tools/retrieve_memory.py +227 -0
  133. jarvis/jarvis_tools/save_memory.py +194 -0
  134. jarvis/jarvis_tools/search_web.py +62 -28
  135. jarvis/jarvis_tools/sub_agent.py +205 -0
  136. jarvis/jarvis_tools/sub_code_agent.py +217 -0
  137. jarvis/jarvis_tools/virtual_tty.py +330 -62
  138. jarvis/jarvis_utils/builtin_replace_map.py +4 -5
  139. jarvis/jarvis_utils/clipboard.py +90 -0
  140. jarvis/jarvis_utils/config.py +607 -50
  141. jarvis/jarvis_utils/embedding.py +3 -0
  142. jarvis/jarvis_utils/fzf.py +57 -0
  143. jarvis/jarvis_utils/git_utils.py +251 -29
  144. jarvis/jarvis_utils/globals.py +174 -17
  145. jarvis/jarvis_utils/http.py +58 -79
  146. jarvis/jarvis_utils/input.py +899 -153
  147. jarvis/jarvis_utils/methodology.py +210 -83
  148. jarvis/jarvis_utils/output.py +220 -137
  149. jarvis/jarvis_utils/utils.py +1906 -135
  150. jarvis_ai_assistant-0.7.0.dist-info/METADATA +465 -0
  151. jarvis_ai_assistant-0.7.0.dist-info/RECORD +192 -0
  152. {jarvis_ai_assistant-0.1.222.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/entry_points.txt +8 -2
  153. jarvis/jarvis_git_details/main.py +0 -265
  154. jarvis/jarvis_platform/oyi.py +0 -357
  155. jarvis/jarvis_tools/edit_file.py +0 -255
  156. jarvis/jarvis_tools/rewrite_file.py +0 -195
  157. jarvis_ai_assistant-0.1.222.dist-info/METADATA +0 -767
  158. jarvis_ai_assistant-0.1.222.dist-info/RECORD +0 -110
  159. /jarvis/{jarvis_git_details → jarvis_memory_organizer}/__init__.py +0 -0
  160. {jarvis_ai_assistant-0.1.222.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/WHEEL +0 -0
  161. {jarvis_ai_assistant-0.1.222.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/licenses/LICENSE +0 -0
  162. {jarvis_ai_assistant-0.1.222.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/top_level.txt +0 -0
@@ -1,357 +0,0 @@
1
- import mimetypes
2
- import os
3
- from typing import Dict, Generator, List, Tuple
4
- from jarvis.jarvis_platform.base import BasePlatform
5
- import json
6
-
7
- from jarvis.jarvis_utils import http
8
- from jarvis.jarvis_utils.output import OutputType, PrettyOutput
9
- from jarvis.jarvis_utils.utils import while_success
10
-
11
-
12
- class OyiModel(BasePlatform):
13
- """Oyi model implementation"""
14
-
15
- BASE_URL = "https://api-10086.rcouyi.com"
16
-
17
- def get_model_list(self) -> List[Tuple[str, str]]:
18
- """Get model list"""
19
- self.get_available_models()
20
- return [(name, info["desc"]) for name, info in self.models.items()]
21
-
22
- def __init__(self):
23
- """Initialize model"""
24
- super().__init__()
25
- self.models = {}
26
- self.messages = []
27
- self.system_prompt = ""
28
- self.conversation = None
29
- self.first_chat = True
30
-
31
- self.token = os.getenv("OYI_API_KEY")
32
- if not self.token:
33
- PrettyOutput.print("OYI_API_KEY 未设置", OutputType.WARNING)
34
-
35
- self.model_name = os.getenv("JARVIS_MODEL") or "deepseek-chat"
36
- if self.model_name not in [m.split()[0] for m in self.get_available_models()]:
37
- PrettyOutput.print(
38
- f"警告: 选择的模型 {self.model_name} 不在可用列表中", OutputType.WARNING
39
- )
40
-
41
- def set_model_name(self, model_name: str):
42
- """Set model name"""
43
-
44
- self.model_name = model_name
45
-
46
- def create_conversation(self) -> bool:
47
- """Create a new conversation"""
48
- try:
49
- headers = {
50
- "Authorization": f"Bearer {self.token}",
51
- "Content-Type": "application/json",
52
- "Accept": "application/json",
53
- "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
54
- }
55
-
56
- payload = {
57
- "id": 0,
58
- "roleId": 0,
59
- "title": "New conversation",
60
- "isLock": False,
61
- "systemMessage": "",
62
- "params": json.dumps(
63
- {
64
- "model": self.model_name,
65
- "is_webSearch": True,
66
- "message": [],
67
- "systemMessage": None,
68
- "requestMsgCount": 65536,
69
- "temperature": 0.8,
70
- "speechVoice": "Alloy",
71
- "max_tokens": 8192,
72
- "chatPluginIds": [],
73
- }
74
- ),
75
- }
76
-
77
- response = while_success(
78
- lambda: http.post(
79
- f"{self.BASE_URL}/chatapi/chat/save", headers=headers, json=payload
80
- ),
81
- sleep_time=5,
82
- )
83
-
84
- data = response.json()
85
- if data["code"] == 200 and data["type"] == "success":
86
- self.conversation = data
87
- return True
88
- else:
89
- PrettyOutput.print(
90
- f"创建会话失败: {data['message']}", OutputType.WARNING
91
- )
92
- return False
93
-
94
- except Exception as e:
95
- PrettyOutput.print(f"创建会话失败: {str(e)}", OutputType.ERROR)
96
- return False
97
-
98
- def set_system_prompt(self, message: str):
99
- """Set system message"""
100
- self.system_prompt = message
101
-
102
- def chat(self, message: str) -> Generator[str, None, None]:
103
- """Execute chat with the model
104
-
105
- Args:
106
- message: User input message
107
-
108
- Returns:
109
- str: Model response
110
- """
111
- try:
112
- # 确保有会话ID
113
- if not self.conversation:
114
- if not self.create_conversation():
115
- raise Exception("Failed to create conversation")
116
-
117
- # 1. 发送消息
118
- headers = {
119
- "Authorization": f"Bearer {self.token}",
120
- "Content-Type": "application/json",
121
- "Accept": "application/json, text/plain, */*",
122
- "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
123
- "Origin": "https://ai.rcouyi.com",
124
- "Referer": "https://ai.rcouyi.com/",
125
- }
126
-
127
- payload = {
128
- "topicId": (
129
- self.conversation["result"]["id"] if self.conversation else None
130
- ),
131
- "messages": self.messages,
132
- "content": message,
133
- "contentFiles": [],
134
- }
135
-
136
- # 如果有上传的文件,添加到请求中
137
- if self.first_chat:
138
- message = self.system_prompt + "\n" + message
139
- payload["content"] = message
140
- self.first_chat = False
141
-
142
- self.messages.append({"role": "user", "content": message})
143
-
144
- # 发送消息
145
- response = while_success(
146
- lambda: http.post(
147
- f"{self.BASE_URL}/chatapi/chat/message",
148
- headers=headers,
149
- json=payload,
150
- ),
151
- sleep_time=5,
152
- )
153
-
154
- data = response.json()
155
- if data["code"] != 200 or data["type"] != "success":
156
- error_msg = f"聊天失败: {data.get('message', '未知错误')}"
157
- PrettyOutput.print(error_msg, OutputType.WARNING)
158
- raise Exception(error_msg)
159
-
160
- message_id = data["result"][-1]
161
-
162
- # 获取响应内容
163
- response = while_success(
164
- lambda: http.stream_post(
165
- f"{self.BASE_URL}/chatapi/chat/message/{message_id}",
166
- headers=headers,
167
- ),
168
- sleep_time=5,
169
- )
170
-
171
- full_response = ""
172
- bin = b""
173
- for chunk in response:
174
- if chunk:
175
- bin += chunk
176
- try:
177
- text = bin.decode("utf-8")
178
- except UnicodeDecodeError:
179
- continue
180
- full_response += text
181
- bin = b""
182
- yield text
183
-
184
- self.messages.append({"role": "assistant", "content": full_response})
185
- return None
186
- except Exception as e:
187
- PrettyOutput.print(f"聊天失败: {str(e)}", OutputType.ERROR)
188
- raise e
189
-
190
- def name(self) -> str:
191
- """Return model name"""
192
- return self.model_name
193
-
194
- @classmethod
195
- def platform_name(cls) -> str:
196
- """Return platform name"""
197
- return "oyi"
198
-
199
- def delete_chat(self) -> bool:
200
- """Delete current chat session"""
201
- try:
202
- if not self.conversation:
203
- return True
204
-
205
- headers = {
206
- "Authorization": f"Bearer {self.token}",
207
- "Content-Type": "application/json",
208
- "Accept": "application/json, text/plain, */*",
209
- "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
210
- "Origin": "https://ai.rcouyi.com",
211
- "Referer": "https://ai.rcouyi.com/",
212
- }
213
-
214
- response = while_success(
215
- lambda: http.post(
216
- f"{self.BASE_URL}/chatapi/chat/{self.conversation['result']['id']}", # type: ignore
217
- headers=headers,
218
- json={},
219
- ),
220
- sleep_time=5,
221
- )
222
-
223
- data = response.json()
224
- if data["code"] == 200 and data["type"] == "success":
225
- self.messages = []
226
- self.conversation = None
227
- self.first_chat = True
228
- return True
229
- else:
230
- error_msg = f"删除会话失败: {data.get('message', '未知错误')}"
231
- PrettyOutput.print(error_msg, OutputType.WARNING)
232
- return False
233
-
234
- except Exception as e:
235
- PrettyOutput.print(f"删除会话失败: {str(e)}", OutputType.ERROR)
236
- return False
237
-
238
- def save(self, file_path: str) -> bool:
239
- """Save chat session to a file."""
240
- if not self.conversation:
241
- PrettyOutput.print("没有活动的会话可供保存", OutputType.WARNING)
242
- return False
243
-
244
- state = {
245
- "conversation": self.conversation,
246
- "messages": self.messages,
247
- "model_name": self.model_name,
248
- "system_prompt": self.system_prompt,
249
- "first_chat": self.first_chat,
250
- }
251
-
252
- try:
253
- with open(file_path, "w", encoding="utf-8") as f:
254
- json.dump(state, f, ensure_ascii=False, indent=4)
255
- self._saved = True
256
- PrettyOutput.print(f"会话已成功保存到 {file_path}", OutputType.SUCCESS)
257
- return True
258
- except Exception as e:
259
- PrettyOutput.print(f"保存会话失败: {str(e)}", OutputType.ERROR)
260
- return False
261
-
262
- def restore(self, file_path: str) -> bool:
263
- """Restore chat session from a file."""
264
- try:
265
- with open(file_path, "r", encoding="utf-8") as f:
266
- state = json.load(f)
267
-
268
- self.conversation = state.get("conversation")
269
- self.messages = state.get("messages", [])
270
- self.model_name = state.get("model_name", "deepseek-chat")
271
- self.system_prompt = state.get("system_prompt", "")
272
- self.first_chat = state.get("first_chat", True)
273
- self._saved = True
274
-
275
- PrettyOutput.print(f"从 {file_path} 成功恢复会话", OutputType.SUCCESS)
276
- return True
277
- except FileNotFoundError:
278
- PrettyOutput.print(f"会话文件未找到: {file_path}", OutputType.ERROR)
279
- return False
280
- except Exception as e:
281
- PrettyOutput.print(f"恢复会话失败: {str(e)}", OutputType.ERROR)
282
- return False
283
-
284
- def get_available_models(self) -> List[str]:
285
- """Get available model list
286
-
287
- Returns:
288
- List[str]: Available model name list
289
- """
290
- try:
291
- if self.models:
292
- return list(self.models.keys())
293
-
294
- headers = {
295
- "Content-Type": "application/json",
296
- "Accept": "application/json, text/plain, */*",
297
- "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
298
- "Origin": "https://ai.rcouyi.com",
299
- "Referer": "https://ai.rcouyi.com/",
300
- }
301
-
302
- response = while_success(
303
- lambda: http.get(
304
- "https://ai.rcouyi.com/config/system.json", headers=headers
305
- ),
306
- sleep_time=5,
307
- )
308
-
309
- data = response.json()
310
-
311
- # 保存模型信息
312
- self.models = {
313
- model["value"]: model
314
- for model in data.get("model", [])
315
- if model.get("enable", False) # 只保存启用的模型
316
- }
317
-
318
- # 格式化显示
319
- models = []
320
- for model in self.models.values():
321
- # 基本信息
322
- model_name = model["value"]
323
- model_str = model["label"]
324
-
325
- # 添加后缀标签
326
- suffix = model.get("suffix", [])
327
- if suffix:
328
- # 处理新格式的suffix (字典列表)
329
- if suffix and isinstance(suffix[0], dict):
330
- suffix_str = ", ".join(s.get("tag", "") for s in suffix)
331
- # 处理旧格式的suffix (字符串列表)
332
- else:
333
- suffix_str = ", ".join(str(s) for s in suffix)
334
- model_str += f" ({suffix_str})"
335
-
336
- # 添加描述或提示
337
- info = model.get("tooltip") or model.get("description", "")
338
- if info:
339
- model_str += f" - {info}"
340
-
341
- model["desc"] = model_str
342
- models.append(model_name)
343
-
344
- return sorted(models)
345
-
346
- except Exception as e:
347
- PrettyOutput.print(f"获取模型列表失败: {str(e)}", OutputType.WARNING)
348
- return []
349
-
350
- def support_upload_files(self) -> bool:
351
- return False
352
-
353
- def support_web(self) -> bool:
354
- return False
355
-
356
- def upload_files(self, file_list: List[str]) -> bool:
357
- return False
@@ -1,255 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- """
3
- 文件编辑工具类
4
-
5
- 功能概述:
6
- 1. 提供精确的文件内容搜索和替换功能,支持多组修改
7
- 2. 支持单个文件的编辑操作,包括创建新文件
8
- 3. 实现原子操作:所有修改要么全部成功,要么全部回滚
9
- 4. 严格匹配控制:每个搜索文本必须且只能匹配一次
10
-
11
- 核心特性:
12
- - 支持不存在的文件和空文件处理
13
- - 自动创建所需目录结构
14
- - 完善的错误处理和回滚机制
15
- - 严格的格式保持要求
16
- """
17
- from typing import Any, Dict
18
-
19
- from jarvis.jarvis_agent.edit_file_handler import EditFileHandler
20
-
21
-
22
- class FileSearchReplaceTool:
23
- name = "edit_file"
24
- description = """代码编辑工具,用于精确修改一个或多个文件
25
-
26
- # 文件编辑工具使用指南
27
-
28
- ## 基本使用
29
- 1. 指定需要修改的文件路径(单个或多个)
30
- 2. 提供一组或多组修改,每个修改包含:
31
- - reason: 修改原因描述
32
- - SEARCH: 需要查找的原始代码(必须包含足够上下文)
33
- - REPLACE: 替换后的新代码
34
- 3. 工具会自动选择最适合的编辑模式
35
-
36
- ## 核心原则
37
- 1. **精准修改**: 只修改必要的代码部分,保持其他部分不变
38
- 2. **最小补丁原则**: 生成最小范围的补丁,包含必要的上下文
39
- 3. **唯一匹配**: 确保搜索文本在文件中唯一匹配
40
- 4. **格式保持**: 严格保持原始代码的格式风格
41
- 5. **部分成功**: 支持多个文件编辑,允许部分文件编辑成功
42
-
43
- """
44
- parameters = {
45
- "type": "object",
46
- "properties": {
47
- "files": {
48
- "type": "array",
49
- "description": "需要修改的文件路径列表",
50
- "items": {
51
- "type": "object",
52
- "properties": {
53
- "path": {"type": "string", "description": "文件路径"},
54
- "changes": {
55
- "type": "array",
56
- "description": "一组或多组修改,每个修改必须包含1-2行上下文用于精确定位",
57
- "items": {
58
- "type": "object",
59
- "properties": {
60
- "reason": {
61
- "type": "string",
62
- "description": "修改的原因",
63
- },
64
- "SEARCH": {
65
- "type": "string",
66
- "description": "需要查找的原始代码",
67
- },
68
- "REPLACE": {
69
- "type": "string",
70
- "description": "替换后的新代码",
71
- },
72
- },
73
- },
74
- },
75
- },
76
- "required": ["path", "changes"],
77
- },
78
- },
79
- },
80
- "required": ["files"],
81
- }
82
-
83
- def execute(self, args: Dict) -> Dict[str, Any]:
84
- """执行文件编辑操作,支持快速编辑和AI辅助编辑两种模式。
85
-
86
- 主要功能:
87
- 1. 处理多个文件的创建或修改,支持不存在的文件
88
- 2. 每个文件独立处理,允许部分文件编辑成功
89
- 3. 自动选择编辑模式(fast_edit或slow_edit)
90
- 4. 保存修改前后的文件状态以便回滚
91
- 5. 提供详细的执行状态输出
92
-
93
- 参数:
94
- args: 包含以下键的字典:
95
- - files: 文件列表,每个文件包含(必填):
96
- - path: 要修改的文件路径
97
- - changes: 修改列表,每个修改包含:
98
- - reason: 修改原因描述
99
- - SEARCH: 需要查找的原始代码(必须包含足够上下文)
100
- - REPLACE: 替换后的新代码
101
-
102
- 返回:
103
- Dict[str, Any] 包含:
104
- - success: 是否至少有一个文件编辑成功(True/False)
105
- - stdout: 成功时的输出消息
106
- - stderr: 失败时的错误消息
107
- - results: 每个文件的处理结果列表
108
-
109
- 异常处理:
110
- 1. 捕获并记录文件操作异常
111
- 2. 失败的修改尝试回滚到原始状态
112
- 3. 新创建的文件在失败时会被删除
113
- 4. 提供3次重试机制确保操作可靠性
114
- 5. 支持大文件处理(自动上传到模型平台)
115
-
116
- 实现细节:
117
- 1. 优先尝试fast_edit模式
118
- 2. 如果fast_edit失败,则尝试slow_edit模式
119
- 3. 严格检查搜索文本的唯一匹配性
120
- 4. 保持原始代码的格式风格
121
- """
122
- import os
123
-
124
- from jarvis.jarvis_utils.output import OutputType, PrettyOutput
125
-
126
- stdout_messages = []
127
- stderr_messages = []
128
- overall_success = False
129
- file_results = []
130
-
131
- for file_info in args["files"]:
132
- file_path = os.path.abspath(file_info["path"])
133
- changes = file_info["changes"]
134
-
135
- # 创建已处理文件变量,用于失败时回滚
136
- original_content = None
137
- processed = False
138
- file_success = True
139
-
140
- try:
141
- file_exists = os.path.exists(file_path)
142
- content = ""
143
-
144
- try:
145
- # 如果文件存在,则读取内容
146
- if file_exists:
147
- with open(file_path, "r", encoding="utf-8") as f:
148
- content = f.read()
149
- original_content = content
150
-
151
- print(f"⚙️ 正在处理文件 {file_path}...")
152
- success, temp_content = EditFileHandler._fast_edit(
153
- file_path, changes
154
- )
155
- if not success:
156
- print(f"❌ 文件 {file_path} 处理失败")
157
- file_results.append(
158
- {
159
- "file": file_path,
160
- "success": False,
161
- "stdout": "",
162
- "stderr": temp_content,
163
- }
164
- )
165
- continue
166
-
167
- print(f"✅ 文件 {file_path} 内容生成完成")
168
-
169
- # 只有当所有替换操作都成功时,才写回文件
170
- if success and (
171
- temp_content != original_content or not file_exists
172
- ):
173
- # 确保目录存在
174
- os.makedirs(
175
- os.path.dirname(os.path.abspath(file_path)), exist_ok=True
176
- )
177
-
178
- with open(file_path, "w", encoding="utf-8") as f:
179
- f.write(temp_content)
180
-
181
- processed = True
182
-
183
- action = "创建并写入" if not file_exists else "成功修改"
184
- stdout_message = f"文件 {file_path} {action} 完成"
185
- stdout_messages.append(stdout_message)
186
- PrettyOutput.print(stdout_message, OutputType.SUCCESS)
187
- overall_success = True
188
-
189
- file_results.append(
190
- {
191
- "file": file_path,
192
- "success": True,
193
- "stdout": stdout_message,
194
- "stderr": "",
195
- }
196
- )
197
-
198
- except Exception as e:
199
- stderr_message = f"处理文件 {file_path} 时出错: {str(e)}"
200
- stderr_messages.append(stderr_message)
201
- PrettyOutput.print(stderr_message, OutputType.WARNING)
202
- file_success = False
203
- file_results.append(
204
- {
205
- "file": file_path,
206
- "success": False,
207
- "stdout": "",
208
- "stderr": stderr_message,
209
- }
210
- )
211
-
212
- except Exception as e:
213
- error_msg = f"文件搜索替换操作失败: {str(e)}"
214
- PrettyOutput.print(error_msg, OutputType.WARNING)
215
-
216
- # 如果有已修改的文件,尝试回滚
217
- if processed:
218
- rollback_message = "操作失败,正在回滚修改..."
219
- stderr_messages.append(rollback_message)
220
- PrettyOutput.print(rollback_message, OutputType.WARNING)
221
-
222
- try:
223
- if original_content is None:
224
- # 如果是新创建的文件,则删除
225
- if os.path.exists(file_path):
226
- os.remove(file_path)
227
- stderr_messages.append(f"已删除新创建的文件: {file_path}")
228
- else:
229
- # 如果是修改的文件,则恢复原内容
230
- with open(file_path, "w", encoding="utf-8") as f:
231
- f.write(original_content)
232
- stderr_messages.append(f"已回滚文件: {file_path}")
233
- except:
234
- stderr_messages.append(f"回滚文件失败: {file_path}")
235
-
236
- file_results.append(
237
- {
238
- "file": file_path,
239
- "success": False,
240
- "stdout": "",
241
- "stderr": error_msg,
242
- }
243
- )
244
-
245
- # 整合所有错误信息到stderr
246
- all_stderr = []
247
- for result in file_results:
248
- if not result["success"]:
249
- all_stderr.append(f"文件 {result['file']} 处理失败: {result['stderr']}")
250
-
251
- return {
252
- "success": overall_success,
253
- "stdout": "\n".join(stdout_messages) if overall_success else "",
254
- "stderr": "\n".join(all_stderr) if not overall_success else "",
255
- }