jarvis-ai-assistant 0.2.1__py3-none-any.whl → 0.2.2__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 (30) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/jarvis_agent/jarvis.py +61 -59
  3. jarvis/jarvis_agent/main.py +42 -40
  4. jarvis/jarvis_code_agent/code_agent.py +35 -31
  5. jarvis/jarvis_code_analysis/code_review.py +73 -39
  6. jarvis/jarvis_git_squash/main.py +16 -12
  7. jarvis/jarvis_git_utils/git_commiter.py +25 -20
  8. jarvis/jarvis_methodology/main.py +34 -49
  9. jarvis/jarvis_multi_agent/main.py +28 -23
  10. jarvis/jarvis_platform/ai8.py +31 -22
  11. jarvis/jarvis_platform/kimi.py +31 -61
  12. jarvis/jarvis_platform/tongyi.py +62 -76
  13. jarvis/jarvis_platform/yuanbao.py +44 -50
  14. jarvis/jarvis_platform_manager/main.py +55 -90
  15. jarvis/jarvis_smart_shell/main.py +58 -87
  16. jarvis/jarvis_tools/cli/main.py +120 -153
  17. jarvis/jarvis_tools/registry.py +1 -7
  18. jarvis/jarvis_tools/search_web.py +12 -10
  19. jarvis/jarvis_utils/http.py +58 -79
  20. jarvis/jarvis_utils/output.py +1 -1
  21. jarvis_ai_assistant-0.2.2.dist-info/METADATA +228 -0
  22. {jarvis_ai_assistant-0.2.1.dist-info → jarvis_ai_assistant-0.2.2.dist-info}/RECORD +26 -29
  23. {jarvis_ai_assistant-0.2.1.dist-info → jarvis_ai_assistant-0.2.2.dist-info}/entry_points.txt +0 -2
  24. jarvis/jarvis_git_details/__init__.py +0 -0
  25. jarvis/jarvis_git_details/main.py +0 -265
  26. jarvis/jarvis_platform/oyi.py +0 -357
  27. jarvis_ai_assistant-0.2.1.dist-info/METADATA +0 -845
  28. {jarvis_ai_assistant-0.2.1.dist-info → jarvis_ai_assistant-0.2.2.dist-info}/WHEEL +0 -0
  29. {jarvis_ai_assistant-0.2.1.dist-info → jarvis_ai_assistant-0.2.2.dist-info}/licenses/LICENSE +0 -0
  30. {jarvis_ai_assistant-0.2.1.dist-info → jarvis_ai_assistant-0.2.2.dist-info}/top_level.txt +0 -0
@@ -1,265 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- """Git Commit分析工具模块
3
-
4
- 该模块提供了一个GitCommitAnalyzer类,用于获取和分析指定Git commit的详细信息,
5
- 包括提交信息、修改内容以及详细的功能、原因和逻辑分析。
6
- """
7
-
8
- import os
9
- import re
10
- import subprocess
11
- from typing import Any, Dict
12
-
13
- from jarvis.jarvis_agent import Agent
14
- from jarvis.jarvis_platform.registry import PlatformRegistry
15
- from jarvis.jarvis_tools.registry import ToolRegistry
16
- from jarvis.jarvis_utils.output import OutputType, PrettyOutput
17
- from jarvis.jarvis_utils.tag import ct, ot
18
- from jarvis.jarvis_utils.utils import init_env
19
-
20
-
21
- class GitCommitAnalyzer:
22
- """Git Commit分析器
23
-
24
- 该类用于获取和分析指定Git commit的详细信息,包括:
25
- - 完整的提交信息
26
- - 修改的文件列表和状态
27
- - 修改的功能、原因和逻辑分析
28
- """
29
-
30
- def execute(self, args: Dict[str, Any]) -> Dict[str, Any]:
31
- """执行commit分析
32
-
33
- Args:
34
- args: 包含commit_sha/commit_range和root_dir的参数字典
35
- commit_sha: 单个commit的SHA
36
- commit_range: 两个commit的SHA范围,格式为"commit1..commit2"
37
- root_dir: 代码库根目录
38
-
39
- Returns:
40
- 包含分析结果的字典
41
- """
42
- try:
43
- commit_sha = args.get("commit_sha")
44
- commit_range = args.get("commit_range")
45
- root_dir = args.get("root_dir", ".")
46
-
47
- if commit_range:
48
- return self.analyze_commit_range(commit_range, root_dir)
49
- elif commit_sha:
50
- return self.analyze_single_commit(commit_sha, root_dir)
51
- else:
52
- raise ValueError("Either commit_sha or commit_range must be provided")
53
- except Exception as e:
54
- return {
55
- "success": False,
56
- "stdout": {},
57
- "stderr": f"Failed to analyze commit: {str(e)}",
58
- }
59
-
60
- def analyze_single_commit(self, commit_sha: str, root_dir: str) -> Dict[str, Any]:
61
- """分析单个commit
62
-
63
- Args:
64
- commit_sha: commit的SHA
65
- root_dir: 代码库根目录
66
-
67
- Returns:
68
- 包含分析结果的字典
69
- """
70
- original_dir = os.getcwd()
71
- try:
72
- os.chdir(root_dir)
73
-
74
- # 获取commit详细信息
75
- commit_info = subprocess.check_output(
76
- f"git show {commit_sha} --pretty=fuller", shell=True, text=True
77
- )
78
-
79
- # 获取commit修改内容
80
- diff_content = subprocess.check_output(
81
- f"git show {commit_sha} --patch", shell=True, text=True
82
- )
83
-
84
- # 分析commit的功能、原因和逻辑
85
- analysis_result = self._analyze_diff_content(diff_content)
86
-
87
- return {
88
- "success": True,
89
- "stdout": {
90
- "commit_info": commit_info,
91
- "diff_content": diff_content,
92
- "analysis_result": analysis_result,
93
- },
94
- "stderr": "",
95
- }
96
- except subprocess.CalledProcessError as error:
97
- return {
98
- "success": False,
99
- "stdout": {},
100
- "stderr": f"Failed to analyze commit: {str(error)}",
101
- }
102
- finally:
103
- os.chdir(original_dir)
104
-
105
- def analyze_commit_range(self, commit_range: str, root_dir: str) -> Dict[str, Any]:
106
- """分析两个commit之间的代码变更
107
-
108
- Args:
109
- commit_range: 两个commit的SHA范围,格式为"commit1..commit2"
110
- root_dir: 代码库根目录
111
-
112
- Returns:
113
- 包含分析结果的字典
114
- """
115
- original_dir = os.getcwd()
116
- try:
117
- os.chdir(root_dir)
118
-
119
- # 获取commit范围差异
120
- diff_content = subprocess.check_output(
121
- f"git diff {commit_range} --patch", shell=True, text=True
122
- )
123
-
124
- # 获取commit范围信息
125
- commit_info = subprocess.check_output(
126
- f"git log {commit_range} --pretty=fuller", shell=True, text=True
127
- )
128
-
129
- # 使用相同的分析方法处理差异内容
130
- analysis_result = self._analyze_diff_content(diff_content)
131
-
132
- return {
133
- "success": True,
134
- "stdout": {
135
- "commit_info": commit_info,
136
- "diff_content": diff_content,
137
- "analysis_result": analysis_result,
138
- },
139
- "stderr": "",
140
- }
141
- except subprocess.CalledProcessError as error:
142
- return {
143
- "success": False,
144
- "stdout": {},
145
- "stderr": f"Failed to analyze commit range: {str(error)}",
146
- }
147
- finally:
148
- os.chdir(original_dir)
149
-
150
- def _analyze_diff_content(self, diff_content: str) -> str:
151
- """分析diff内容并生成报告
152
-
153
- Args:
154
- diff_content: git diff或git show的输出内容
155
-
156
- Returns:
157
- 分析结果字符串
158
- """
159
- system_prompt = """你是一位资深代码分析专家,拥有多年代码审查和重构经验。你需要对Git commit进行深入分析,包括:
160
- 1. 修改的功能:明确说明本次commit实现或修改了哪些功能
161
- 2. 修改的原因:分析为什么要进行这些修改(如修复bug、优化性能、添加新功能等)
162
- 3. 修改的逻辑:详细说明代码修改的具体实现逻辑和思路
163
- 4. 影响范围:评估本次修改可能影响的其他模块或功能
164
- 5. 代码质量:分析代码风格、可读性和可维护性
165
- 6. 测试覆盖:评估是否需要添加或修改测试用例
166
- 7. 最佳实践:检查代码是否符合行业最佳实践和项目规范
167
-
168
- 请确保分析内容:
169
- - 准确反映commit的实际修改
170
- - 提供足够的技术细节
171
- - 保持结构清晰,便于理解
172
- - 重点关注关键修改和潜在风险"""
173
-
174
- tool_registry = ToolRegistry()
175
- agent = Agent(
176
- system_prompt=system_prompt,
177
- name="Commit Analysis Agent",
178
- summary_prompt=f"""请生成一份详细的commit分析报告,包含以下内容:
179
- {ot("REPORT")}
180
- # 功能分析
181
- [说明本次commit实现或修改了哪些功能]
182
-
183
- # 修改原因
184
- [分析进行这些修改的原因,如修复bug、优化性能、添加新功能等]
185
-
186
- # 实现逻辑
187
- [详细说明代码修改的具体实现逻辑和思路]
188
-
189
- # 影响范围
190
- [评估本次修改可能影响的其他模块或功能]
191
-
192
- # 代码质量
193
- [分析代码风格、可读性和可维护性]
194
-
195
- # 测试覆盖
196
- [评估是否需要添加或修改测试用例]
197
-
198
- # 最佳实践
199
- [检查代码是否符合行业最佳实践和项目规范]
200
- {ct("REPORT")}""",
201
- output_handler=[tool_registry],
202
- llm_type="normal",
203
- auto_complete=True,
204
- )
205
-
206
- return agent.run(diff_content)
207
-
208
-
209
- def extract_analysis_report(result: str) -> str:
210
- """从分析结果中提取报告内容
211
-
212
- Args:
213
- result: 包含REPORT标签的完整分析结果字符串
214
-
215
- Returns:
216
- 提取的报告内容,如果未找到REPORT标签则返回空字符串
217
- """
218
- search_match = re.search(
219
- ot("REPORT") + r"\n(.*?)\n" + ct("REPORT"), result, re.DOTALL
220
- )
221
- if search_match:
222
- return search_match.group(1)
223
- return result
224
-
225
-
226
- def main():
227
- """主函数,用于命令行接口"""
228
- import argparse
229
-
230
- init_env("欢迎使用 Jarvis-GitCommitAnalyzer,您的Git Commit分析助手已准备就绪!")
231
-
232
- parser = argparse.ArgumentParser(description="Git Commit Analyzer")
233
- group = parser.add_mutually_exclusive_group(required=True)
234
- group.add_argument("commit", nargs="?", help="Commit SHA to analyze")
235
- group.add_argument(
236
- "--range", type=str, help="Commit range to analyze (commit1..commit2)"
237
- )
238
- parser.add_argument(
239
- "--root-dir", type=str, help="Root directory of the codebase", default="."
240
- )
241
-
242
- args = parser.parse_args()
243
-
244
- analyzer = GitCommitAnalyzer()
245
- if args.range:
246
- result = analyzer.execute(
247
- {"commit_range": args.range, "root_dir": args.root_dir}
248
- )
249
- else:
250
- result = analyzer.execute(
251
- {"commit_sha": args.commit, "root_dir": args.root_dir}
252
- )
253
-
254
- if result["success"]:
255
- PrettyOutput.section("Commit Information:", OutputType.SUCCESS)
256
- PrettyOutput.print(result["stdout"]["commit_info"], OutputType.CODE)
257
- PrettyOutput.section("Analysis Report:", OutputType.SUCCESS)
258
- report = extract_analysis_report(result["stdout"]["analysis_result"])
259
- PrettyOutput.print(report, OutputType.SUCCESS, lang="markdown")
260
- else:
261
- PrettyOutput.print(result["stderr"], OutputType.WARNING)
262
-
263
-
264
- if __name__ == "__main__":
265
- main()
@@ -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