jarvis-ai-assistant 0.1.148__py3-none-any.whl → 0.1.150__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.

Files changed (41) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/jarvis_agent/jarvis.py +7 -5
  3. jarvis/jarvis_agent/main.py +0 -1
  4. jarvis/jarvis_agent/patch.py +42 -7
  5. jarvis/jarvis_code_analysis/code_review.py +0 -1
  6. jarvis/jarvis_git_utils/git_commiter.py +0 -1
  7. jarvis/jarvis_lsp/base.py +1 -1
  8. jarvis/jarvis_lsp/cpp.py +1 -1
  9. jarvis/jarvis_lsp/go.py +1 -1
  10. jarvis/jarvis_lsp/python.py +1 -1
  11. jarvis/jarvis_lsp/registry.py +2 -1
  12. jarvis/jarvis_lsp/rust.py +1 -1
  13. jarvis/jarvis_mcp/__init__.py +36 -0
  14. jarvis/jarvis_mcp/local_mcp_client.py +241 -0
  15. jarvis/jarvis_mcp/remote_mcp_client.py +230 -0
  16. jarvis/jarvis_platform/base.py +1 -1
  17. jarvis/jarvis_platform/kimi.py +3 -3
  18. jarvis/jarvis_platform/registry.py +2 -4
  19. jarvis/jarvis_platform/yuanbao.py +4 -4
  20. jarvis/jarvis_tools/code_plan.py +0 -1
  21. jarvis/jarvis_tools/file_analyzer.py +1 -1
  22. jarvis/jarvis_tools/file_operation.py +7 -26
  23. jarvis/jarvis_tools/methodology.py +2 -1
  24. jarvis/jarvis_tools/read_code.py +0 -1
  25. jarvis/jarvis_tools/registry.py +108 -3
  26. jarvis/jarvis_tools/search_web.py +0 -1
  27. jarvis/jarvis_utils/config.py +14 -8
  28. jarvis/jarvis_utils/embedding.py +4 -8
  29. jarvis/jarvis_utils/file_processors.py +0 -262
  30. jarvis/jarvis_utils/git_utils.py +19 -8
  31. jarvis/jarvis_utils/input.py +7 -1
  32. jarvis/jarvis_utils/methodology.py +2 -2
  33. jarvis/jarvis_utils/output.py +0 -1
  34. jarvis/jarvis_utils/utils.py +23 -221
  35. {jarvis_ai_assistant-0.1.148.dist-info → jarvis_ai_assistant-0.1.150.dist-info}/METADATA +31 -38
  36. {jarvis_ai_assistant-0.1.148.dist-info → jarvis_ai_assistant-0.1.150.dist-info}/RECORD +40 -38
  37. jarvis/jarvis_platform_manager/openai_test.py +0 -138
  38. {jarvis_ai_assistant-0.1.148.dist-info → jarvis_ai_assistant-0.1.150.dist-info}/LICENSE +0 -0
  39. {jarvis_ai_assistant-0.1.148.dist-info → jarvis_ai_assistant-0.1.150.dist-info}/WHEEL +0 -0
  40. {jarvis_ai_assistant-0.1.148.dist-info → jarvis_ai_assistant-0.1.150.dist-info}/entry_points.txt +0 -0
  41. {jarvis_ai_assistant-0.1.148.dist-info → jarvis_ai_assistant-0.1.150.dist-info}/top_level.txt +0 -0
jarvis/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  """Jarvis AI Assistant"""
2
2
 
3
- __version__ = "0.1.148"
3
+ __version__ = "0.1.150"
@@ -4,6 +4,7 @@ import sys
4
4
 
5
5
  from typing import Dict # 仅保留实际使用的类型导入
6
6
 
7
+ from jarvis.jarvis_utils.config import get_data_dir
7
8
  from prompt_toolkit import prompt
8
9
  import yaml
9
10
  from yaspin import yaspin
@@ -25,13 +26,14 @@ def _load_tasks() -> Dict[str, str]:
25
26
  """Load tasks from .jarvis files in user home and current directory."""
26
27
  tasks: Dict[str, str] = {}
27
28
 
28
- # Check .jarvis/pre-command in user directory
29
- user_jarvis = os.path.expanduser("~/.jarvis/pre-command")
30
- if os.path.exists(user_jarvis):
31
- spinner_text = f"从{user_jarvis}加载预定义任务..."
29
+ # Check pre-command in data directory
30
+ data_dir = get_data_dir()
31
+ pre_command_path = os.path.join(data_dir, "pre-command")
32
+ if os.path.exists(pre_command_path):
33
+ spinner_text = f"从{pre_command_path}加载预定义任务..."
32
34
  with yaspin(text=spinner_text, color="cyan") as spinner:
33
35
  try:
34
- with open(user_jarvis, "r", encoding="utf-8", errors="ignore") as f:
36
+ with open(pre_command_path, "r", encoding="utf-8", errors="ignore") as f:
35
37
  user_tasks = yaml.safe_load(f)
36
38
  if isinstance(user_tasks, dict):
37
39
  for name, desc in user_tasks.items():
@@ -1,7 +1,6 @@
1
1
  import argparse
2
2
  import yaml
3
3
  import os
4
- from typing import Optional, List
5
4
  from jarvis.jarvis_agent import Agent
6
5
  from jarvis.jarvis_utils.input import get_multiline_input
7
6
  from jarvis.jarvis_utils.output import PrettyOutput, OutputType
@@ -238,7 +238,8 @@ def revert_file(filepath: str):
238
238
  # 检查文件是否在版本控制中
239
239
  result = subprocess.run(
240
240
  ['git', 'ls-files', '--error-unmatch', filepath],
241
- stderr=subprocess.PIPE
241
+ stderr=subprocess.PIPE,
242
+ text=False # 禁用自动文本解码
242
243
  )
243
244
  if result.returncode == 0:
244
245
  subprocess.run(['git', 'checkout', 'HEAD',
@@ -248,33 +249,67 @@ def revert_file(filepath: str):
248
249
  os.remove(filepath)
249
250
  subprocess.run(['git', 'clean', '-f', '--', filepath], check=True)
250
251
  except subprocess.CalledProcessError as e:
251
- PrettyOutput.print(f"恢复文件失败: {str(e)}", OutputType.ERROR)
252
+ error_msg = e.stderr.decode('utf-8', errors='replace') if e.stderr else str(e)
253
+ PrettyOutput.print(f"恢复文件失败: {error_msg}", OutputType.ERROR)
252
254
  # 修改后的恢复函数
253
255
 
254
256
 
255
257
  def revert_change():
258
+ """恢复所有未提交的修改到HEAD状态"""
256
259
  import subprocess
257
- subprocess.run(['git', 'reset', '--hard', 'HEAD'], check=True)
258
- subprocess.run(['git', 'clean', '-fd'], check=True)
260
+ try:
261
+ # 检查是否为空仓库
262
+ head_check = subprocess.run(
263
+ ['git', 'rev-parse', '--verify', 'HEAD'],
264
+ stderr=subprocess.PIPE,
265
+ stdout=subprocess.PIPE
266
+ )
267
+ if head_check.returncode == 0:
268
+ subprocess.run(['git', 'reset', '--hard', 'HEAD'], check=True)
269
+ subprocess.run(['git', 'clean', '-fd'], check=True)
270
+ except subprocess.CalledProcessError as e:
271
+ return f"恢复更改失败: {str(e)}"
259
272
  # 修改后的获取差异函数
260
273
 
261
274
 
262
275
  def get_diff() -> str:
263
276
  """使用git获取暂存区差异"""
264
277
  import subprocess
278
+
279
+ # 初始化状态
280
+ need_reset = False
281
+
265
282
  try:
283
+ # 暂存所有修改
266
284
  subprocess.run(['git', 'add', '.'], check=True)
285
+ need_reset = True
286
+
287
+ # 获取差异
267
288
  result = subprocess.run(
268
289
  ['git', 'diff', '--cached'],
269
290
  capture_output=True,
270
- text=True,
291
+ text=False,
271
292
  check=True
272
293
  )
273
- ret = result.stdout
274
- subprocess.run(['git', "reset", "--mixed", "HEAD"], check=True)
294
+
295
+ # 解码输出
296
+ try:
297
+ ret = result.stdout.decode('utf-8')
298
+ except UnicodeDecodeError:
299
+ ret = result.stdout.decode('utf-8', errors='replace')
300
+
301
+ # 重置暂存区
302
+ subprocess.run(['git', "reset", "--mixed"], check=False)
275
303
  return ret
304
+
276
305
  except subprocess.CalledProcessError as e:
306
+ if need_reset:
307
+ subprocess.run(['git', "reset", "--mixed"], check=False)
277
308
  return f"获取差异失败: {str(e)}"
309
+ except Exception as e:
310
+ if need_reset:
311
+ subprocess.run(['git', "reset", "--mixed"], check=False)
312
+ return f"发生意外错误: {str(e)}"
278
313
 
279
314
 
280
315
  def handle_commit_workflow() -> bool:
@@ -4,7 +4,6 @@ import os
4
4
  import re
5
5
  import tempfile
6
6
 
7
- from httpx import get
8
7
  from yaspin import yaspin
9
8
  from jarvis.jarvis_platform.registry import PlatformRegistry
10
9
  from jarvis.jarvis_tools.read_code import ReadCodeTool
@@ -3,7 +3,6 @@ import shlex
3
3
  import subprocess
4
4
  from typing import Dict, Any, Optional
5
5
  import tempfile
6
- from click import Option
7
6
  import yaml
8
7
  from yaspin import yaspin
9
8
  from jarvis.jarvis_platform.registry import PlatformRegistry
jarvis/jarvis_lsp/base.py CHANGED
@@ -1,5 +1,5 @@
1
1
  from abc import ABC, abstractmethod
2
- from typing import List, Dict, Optional, Tuple, Any, Union
2
+ from typing import List, Dict, Any, Union
3
3
 
4
4
  class BaseLSP(ABC):
5
5
  """Base class for Language Server Protocol integration.
jarvis/jarvis_lsp/cpp.py CHANGED
@@ -1,7 +1,7 @@
1
1
  import os
2
2
  import shutil
3
3
  import subprocess
4
- from typing import List, Dict, Optional, Tuple, Any
4
+ from typing import List, Dict, Optional, Any
5
5
  import json
6
6
  from jarvis.jarvis_lsp.base import BaseLSP
7
7
  from jarvis.jarvis_utils.output import OutputType, PrettyOutput
jarvis/jarvis_lsp/go.py CHANGED
@@ -1,7 +1,7 @@
1
1
  import os
2
2
  import shutil
3
3
  import subprocess
4
- from typing import List, Dict, Optional, Tuple, Any
4
+ from typing import List, Dict, Optional, Any
5
5
  import json
6
6
  from jarvis.jarvis_lsp.base import BaseLSP
7
7
  from jarvis.jarvis_utils.output import OutputType, PrettyOutput
@@ -1,4 +1,4 @@
1
- from typing import List, Dict, Optional, Tuple, Any
1
+ from typing import List, Dict, Any
2
2
  import jedi
3
3
  from jarvis.jarvis_lsp.base import BaseLSP
4
4
 
@@ -6,6 +6,7 @@ import sys
6
6
  from typing import Dict, Type, Optional, List
7
7
  from jarvis.jarvis_lsp.base import BaseLSP
8
8
  from jarvis.jarvis_utils.output import OutputType, PrettyOutput
9
+ from jarvis.jarvis_utils.config import get_data_dir
9
10
 
10
11
  REQUIRED_METHODS = [
11
12
  ('initialize', ['workspace_path']),
@@ -21,7 +22,7 @@ class LSPRegistry:
21
22
  @staticmethod
22
23
  def get_lsp_dir() -> str:
23
24
  """Get LSP implementation directory."""
24
- user_lsp_dir = os.path.expanduser("~/.jarvis/lsp")
25
+ user_lsp_dir = os.path.join(get_data_dir(), "lsp")
25
26
  if not os.path.exists(user_lsp_dir):
26
27
  try:
27
28
  os.makedirs(user_lsp_dir)
jarvis/jarvis_lsp/rust.py CHANGED
@@ -1,7 +1,7 @@
1
1
  import os
2
2
  import shutil
3
3
  import subprocess
4
- from typing import List, Dict, Optional, Tuple, Any
4
+ from typing import List, Dict, Optional, Any
5
5
  import json
6
6
  from jarvis.jarvis_lsp.base import BaseLSP
7
7
  from jarvis.jarvis_utils.output import OutputType, PrettyOutput
@@ -0,0 +1,36 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import Any, Dict, List
3
+
4
+
5
+ class McpClient(ABC):
6
+ """MCP客户端抽象基类"""
7
+
8
+ @abstractmethod
9
+ def get_tool_list(self) -> List[Dict[str, Any]]:
10
+ """获取工具列表
11
+
12
+ 返回:
13
+ List[Dict[str, Any]]: 工具列表,每个工具包含以下字段:
14
+ - name: str - 工具名称
15
+ - description: str - 工具描述
16
+ - parameters: Dict - 工具参数
17
+ """
18
+ pass
19
+
20
+ @abstractmethod
21
+ def execute(self, tool_name: str, arguments: Dict[str, Any]) -> Dict[str, Any]:
22
+ """执行工具
23
+
24
+ 参数:
25
+ tool_name: 工具名称
26
+ arguments: 参数字典,包含工具执行所需的参数
27
+
28
+ 返回:
29
+ Dict[str, Any]: 执行结果,包含以下字段:
30
+ - success: bool - 是否执行成功
31
+ - stdout: str - 标准输出
32
+ - stderr: str - 标准错误
33
+ """
34
+ pass
35
+
36
+
@@ -0,0 +1,241 @@
1
+ from typing import Any, Dict, List
2
+ import subprocess
3
+ import os
4
+ import json
5
+ from jarvis.jarvis_utils.output import OutputType, PrettyOutput
6
+ from . import McpClient
7
+
8
+
9
+ class LocalMcpClient(McpClient):
10
+ """本地MCP客户端实现
11
+
12
+ 参数:
13
+ config: 配置字典(command、args、env)
14
+ """
15
+ def __init__(self, config: Dict[str, Any]):
16
+ self.config = config
17
+ self.process = None
18
+ self.protocol_version = "2025-03-26" # MCP协议版本
19
+ self._start_process()
20
+ self._initialize()
21
+
22
+ def _start_process(self) -> None:
23
+ """启动MCP进程"""
24
+ try:
25
+ # 构建命令和参数
26
+ command = self.config.get('command', '')
27
+ if not command:
28
+ raise ValueError('No command specified in config')
29
+
30
+ # 获取参数列表
31
+ args = self.config.get('args', [])
32
+ if not isinstance(args, list):
33
+ args = [str(args)]
34
+
35
+ # 获取环境变量
36
+ env = os.environ.copy()
37
+ env.update(self.config.get('env', {}))
38
+
39
+ # 启动进程
40
+ self.process = subprocess.Popen(
41
+ [command] + args,
42
+ stdin=subprocess.PIPE,
43
+ stdout=subprocess.PIPE,
44
+ stderr=subprocess.PIPE,
45
+ env=env,
46
+ text=True
47
+ )
48
+
49
+ except Exception as e:
50
+ PrettyOutput.print(f"启动MCP进程失败: {str(e)}", OutputType.ERROR)
51
+ raise
52
+
53
+ def _initialize(self) -> None:
54
+ """初始化MCP连接"""
55
+ try:
56
+ # 发送初始化请求
57
+ response = self._send_request('initialize', {
58
+ 'processId': os.getpid(),
59
+ 'clientInfo': {
60
+ 'name': 'jarvis',
61
+ 'version': '1.0.0'
62
+ },
63
+ 'capabilities': {},
64
+ 'protocolVersion': self.protocol_version
65
+ })
66
+
67
+ # 验证服务器响应
68
+ if 'result' not in response:
69
+ raise RuntimeError(f"初始化失败: {response.get('error', 'Unknown error')}")
70
+
71
+ result = response['result']
72
+
73
+ # 发送initialized通知 - 使用正确的方法名格式
74
+ self._send_notification('notifications/initialized', {})
75
+
76
+ except Exception as e:
77
+ PrettyOutput.print(f"MCP初始化失败: {str(e)}", OutputType.ERROR)
78
+ raise
79
+
80
+ def _send_request(self, method: str, params: Dict[str, Any]) -> Dict[str, Any]:
81
+ """发送请求到MCP进程
82
+
83
+ 参数:
84
+ method: 请求方法
85
+ params: 请求参数
86
+
87
+ 返回:
88
+ Dict[str, Any]: 响应结果
89
+ """
90
+ if not self.process:
91
+ raise RuntimeError('MCP process not started')
92
+
93
+ try:
94
+ # 构建请求
95
+ request = {
96
+ 'jsonrpc': '2.0',
97
+ 'method': method,
98
+ 'params': params,
99
+ 'id': 1
100
+ }
101
+
102
+ # 发送请求
103
+ self.process.stdin.write(json.dumps(request) + '\n') # type: ignore
104
+ self.process.stdin.flush() # type: ignore
105
+
106
+ # 读取响应
107
+ response = self.process.stdout.readline() # type: ignore
108
+ return json.loads(response)
109
+
110
+ except Exception as e:
111
+ PrettyOutput.print(f"发送请求失败: {str(e)}", OutputType.ERROR)
112
+ raise
113
+
114
+ def _send_notification(self, method: str, params: Dict[str, Any]) -> None:
115
+ """发送通知到MCP进程(不需要响应)
116
+
117
+ 参数:
118
+ method: 通知方法
119
+ params: 通知参数
120
+ """
121
+ if not self.process:
122
+ raise RuntimeError('MCP process not started')
123
+
124
+ try:
125
+ # 构建通知
126
+ notification = {
127
+ 'jsonrpc': '2.0',
128
+ 'method': method,
129
+ 'params': params
130
+ }
131
+ # 发送通知
132
+ self.process.stdin.write(json.dumps(notification) + '\n') # type: ignore
133
+ self.process.stdin.flush() # type: ignore
134
+
135
+ except Exception as e:
136
+ PrettyOutput.print(f"发送通知失败: {str(e)}", OutputType.ERROR)
137
+ raise
138
+
139
+ def get_tool_list(self) -> List[Dict[str, Any]]:
140
+ """获取工具列表
141
+
142
+ 返回:
143
+ List[Dict[str, Any]]: 工具列表,每个工具包含以下字段:
144
+ - name: str - 工具名称
145
+ - description: str - 工具描述
146
+ - parameters: Dict - 工具参数
147
+ """
148
+ try:
149
+ response = self._send_request('tools/list', {})
150
+ if 'result' in response and 'tools' in response['result']:
151
+ # 注意这里: 响应结构是 response['result']['tools']
152
+ tools = response['result']['tools']
153
+ # 将MCP协议字段转换为内部格式
154
+ formatted_tools = []
155
+ for tool in tools:
156
+ # 从inputSchema中提取参数定义
157
+ input_schema = tool.get('inputSchema', {})
158
+ parameters = {}
159
+ if 'properties' in input_schema:
160
+ parameters = input_schema['properties']
161
+
162
+ formatted_tools.append({
163
+ 'name': tool.get('name', ''),
164
+ 'description': tool.get('description', ''),
165
+ 'parameters': parameters
166
+ })
167
+ return formatted_tools
168
+ else:
169
+ error_msg = "获取工具列表失败"
170
+ if 'error' in response:
171
+ error_msg += f": {response['error']}"
172
+ elif 'result' in response:
173
+ error_msg += f": 响应格式不正确 - {response['result']}"
174
+ else:
175
+ error_msg += ": 未知错误"
176
+
177
+ PrettyOutput.print(error_msg, OutputType.ERROR)
178
+ return []
179
+ except Exception as e:
180
+ PrettyOutput.print(f"获取工具列表失败: {str(e)}", OutputType.ERROR)
181
+ return []
182
+
183
+ def execute(self, tool_name: str, arguments: Dict[str, Any]) -> Dict[str, Any]:
184
+ """执行工具
185
+
186
+ 参数:
187
+ tool_name: 工具名称
188
+ arguments: 参数字典,包含工具执行所需的参数
189
+
190
+ 返回:
191
+ Dict[str, Any]: 执行结果,包含以下字段:
192
+ - success: bool - 是否执行成功
193
+ - stdout: str - 标准输出
194
+ - stderr: str - 标准错误
195
+ """
196
+ try:
197
+ response = self._send_request('tools/call', {
198
+ 'name': tool_name,
199
+ 'arguments': arguments
200
+ })
201
+ if 'result' in response:
202
+ result = response['result']
203
+ # 从content中提取输出信息
204
+ stdout = ''
205
+ stderr = ''
206
+ for content in result.get('content', []):
207
+ if content.get('type') == 'text':
208
+ stdout += content.get('text', '')
209
+ elif content.get('type') == 'error':
210
+ stderr += content.get('text', '')
211
+
212
+ return {
213
+ 'success': True,
214
+ 'stdout': stdout,
215
+ 'stderr': stderr
216
+ }
217
+ else:
218
+ return {
219
+ 'success': False,
220
+ 'stdout': '',
221
+ 'stderr': response.get('error', 'Unknown error')
222
+ }
223
+ except Exception as e:
224
+ PrettyOutput.print(f"执行工具失败: {str(e)}", OutputType.ERROR)
225
+ return {
226
+ 'success': False,
227
+ 'stdout': '',
228
+ 'stderr': str(e)
229
+ }
230
+
231
+ def __del__(self):
232
+ """清理资源"""
233
+ if self.process:
234
+ try:
235
+ # 发送退出通知 - 使用通知而非请求
236
+ self._send_notification('notifications/exit', {})
237
+ # 等待进程结束
238
+ self.process.wait(timeout=1)
239
+ except:
240
+ # 如果进程没有正常退出,强制终止
241
+ self.process.kill()