jarvis-ai-assistant 0.3.2__py3-none-any.whl → 0.3.4__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 (27) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/jarvis_agent/__init__.py +170 -9
  3. jarvis/jarvis_agent/file_methodology_manager.py +6 -1
  4. jarvis/jarvis_agent/share_manager.py +21 -0
  5. jarvis/jarvis_agent/tool_executor.py +8 -4
  6. jarvis/jarvis_code_agent/code_agent.py +7 -12
  7. jarvis/jarvis_code_analysis/code_review.py +117 -12
  8. jarvis/jarvis_git_utils/git_commiter.py +63 -9
  9. jarvis/jarvis_memory_organizer/__init__.py +0 -0
  10. jarvis/jarvis_memory_organizer/memory_organizer.py +729 -0
  11. jarvis/jarvis_platform/base.py +9 -0
  12. jarvis/jarvis_platform/kimi.py +20 -0
  13. jarvis/jarvis_platform/openai.py +27 -0
  14. jarvis/jarvis_platform/tongyi.py +19 -0
  15. jarvis/jarvis_platform/yuanbao.py +18 -0
  16. jarvis/jarvis_platform_manager/main.py +22 -16
  17. jarvis/jarvis_tools/base.py +8 -1
  18. jarvis/jarvis_utils/git_utils.py +20 -3
  19. jarvis/jarvis_utils/globals.py +16 -10
  20. jarvis/jarvis_utils/methodology.py +19 -2
  21. jarvis/jarvis_utils/utils.py +159 -78
  22. {jarvis_ai_assistant-0.3.2.dist-info → jarvis_ai_assistant-0.3.4.dist-info}/METADATA +40 -1
  23. {jarvis_ai_assistant-0.3.2.dist-info → jarvis_ai_assistant-0.3.4.dist-info}/RECORD +27 -25
  24. {jarvis_ai_assistant-0.3.2.dist-info → jarvis_ai_assistant-0.3.4.dist-info}/entry_points.txt +2 -0
  25. {jarvis_ai_assistant-0.3.2.dist-info → jarvis_ai_assistant-0.3.4.dist-info}/WHEEL +0 -0
  26. {jarvis_ai_assistant-0.3.2.dist-info → jarvis_ai_assistant-0.3.4.dist-info}/licenses/LICENSE +0 -0
  27. {jarvis_ai_assistant-0.3.2.dist-info → jarvis_ai_assistant-0.3.4.dist-info}/top_level.txt +0 -0
@@ -255,6 +255,15 @@ class BasePlatform(ABC):
255
255
  """Get env default values"""
256
256
  return {}
257
257
 
258
+ @classmethod
259
+ def get_env_config_guide(cls) -> Dict[str, str]:
260
+ """Get environment variable configuration guide
261
+
262
+ Returns:
263
+ Dict[str, str]: A dictionary mapping env key names to their configuration instructions
264
+ """
265
+ return {}
266
+
258
267
  def set_suppress_output(self, suppress: bool):
259
268
  """Set whether to suppress output"""
260
269
  self.suppress_output = suppress
@@ -405,3 +405,23 @@ class KimiModel(BasePlatform):
405
405
  List[str]: 环境变量键的列表
406
406
  """
407
407
  return ["KIMI_API_KEY"]
408
+
409
+ @classmethod
410
+ def get_env_config_guide(cls) -> Dict[str, str]:
411
+ """
412
+ 获取环境变量配置指导
413
+
414
+ 返回:
415
+ Dict[str, str]: 环境变量名到配置指导的映射
416
+ """
417
+ return {
418
+ "KIMI_API_KEY": (
419
+ "1. 登录 Kimi 网页版: https://kimi.moonshot.cn/\n"
420
+ "2. 打开浏览器开发者工具 (F12)\n"
421
+ '3. 切换到"网络"(Network)标签页\n'
422
+ "4. 在 Kimi 中发送一条消息\n"
423
+ "5. 找到 stream 请求\n"
424
+ '6. 在"请求标头"中找到 authorization 字段\n'
425
+ "7. 复制 Bearer 后面的 API Key 部分"
426
+ )
427
+ }
@@ -222,6 +222,33 @@ class OpenAIModel(BasePlatform):
222
222
  """
223
223
  return ["OPENAI_API_KEY", "OPENAI_API_BASE"]
224
224
 
225
+ @classmethod
226
+ def get_env_config_guide(cls) -> Dict[str, str]:
227
+ """
228
+ 获取环境变量配置指导
229
+
230
+ 返回:
231
+ Dict[str, str]: 环境变量名到配置指导的映射
232
+ """
233
+ return {
234
+ "OPENAI_API_KEY": (
235
+ "请输入您的 OpenAI API Key:\n"
236
+ "获取方式一(官方):\n"
237
+ "1. 登录 OpenAI 平台: https://platform.openai.com/\n"
238
+ "2. 进入 API Keys 页面\n"
239
+ "3. 创建新的 API Key 或使用现有的\n"
240
+ "4. 复制 API Key (以 sk- 开头)\n"
241
+ "\n获取方式二(第三方代理):\n"
242
+ "如果使用第三方代理服务,请从代理服务商处获取 API Key"
243
+ ),
244
+ "OPENAI_API_BASE": (
245
+ "请输入 API Base URL:\n"
246
+ "- 官方 API: https://api.openai.com/v1\n"
247
+ "- 如使用代理或第三方服务,请输入对应的 Base URL\n"
248
+ "- 例如: https://your-proxy.com/v1"
249
+ ),
250
+ }
251
+
225
252
  @classmethod
226
253
  def get_env_defaults(cls) -> Dict[str, str]:
227
254
  """
@@ -567,3 +567,22 @@ class TongyiPlatform(BasePlatform):
567
567
  List[str]: 环境变量键的列表
568
568
  """
569
569
  return ["TONGYI_COOKIES"]
570
+
571
+ @classmethod
572
+ def get_env_config_guide(cls) -> Dict[str, str]:
573
+ """
574
+ 获取环境变量配置指导
575
+
576
+ 返回:
577
+ Dict[str, str]: 环境变量名到配置指导的映射
578
+ """
579
+ return {
580
+ "TONGYI_COOKIES": (
581
+ "1. 登录通义千问网页版: https://tongyi.aliyun.com/\n"
582
+ "2. 打开浏览器开发者工具 (F12)\n"
583
+ '3. 切换到"网络"(Network)标签页\n'
584
+ "4. 刷新页面或发送一条消息\n"
585
+ "5. 找到 conversation 请求或任意发往 api.tongyi.com 的请求\n"
586
+ '6. 在"请求标头"中复制完整的 Cookie 值'
587
+ )
588
+ }
@@ -633,3 +633,21 @@ class YuanbaoPlatform(BasePlatform):
633
633
  List[str]: 环境变量键的列表
634
634
  """
635
635
  return ["YUANBAO_COOKIES"]
636
+
637
+ @classmethod
638
+ def get_env_config_guide(cls) -> Dict[str, str]:
639
+ """
640
+ 获取环境变量配置指导
641
+
642
+ 返回:
643
+ Dict[str, str]: 环境变量名到配置指导的映射
644
+ """
645
+ return {
646
+ "YUANBAO_COOKIES": (
647
+ "1. 登录腾讯元宝网页版: https://yuanbao.tencent.com/\n"
648
+ "2. 打开浏览器开发者工具 (F12)\n"
649
+ '3. 切换到"网络"(Network)标签页\n'
650
+ "4. 刷新页面,找到任意一个发往 yuanbao.tencent.com 的请求\n"
651
+ '5. 在"请求标头"中复制完整的 Cookie 值'
652
+ )
653
+ }
@@ -119,8 +119,14 @@ def chat_with_model(
119
119
  PrettyOutput.print("检测到空输入,退出聊天", OutputType.INFO)
120
120
  break
121
121
 
122
+ # Parse command and arguments
123
+ stripped_input = user_input.strip()
124
+ parts = stripped_input.split(None, 1)
125
+ command = parts[0] if parts else ""
126
+ args = parts[1] if len(parts) > 1 else ""
127
+
122
128
  # Check if it is a clear session command
123
- if user_input.strip() == "/clear":
129
+ if command == "/clear":
124
130
  try:
125
131
  platform.reset() # type: ignore[no-untyped-call] # type: ignore[no-untyped-call] # type: ignore[no-untyped-call]
126
132
  platform.set_model_name(model_name) # Reinitialize session
@@ -131,9 +137,9 @@ def chat_with_model(
131
137
  continue
132
138
 
133
139
  # Check if it is an upload command
134
- if user_input.strip().startswith("/upload"):
140
+ if command == "/upload":
135
141
  try:
136
- file_path = user_input.strip()[8:].strip()
142
+ file_path = args
137
143
  if not file_path:
138
144
  PrettyOutput.print(
139
145
  '请指定要上传的文件路径,例如: /upload /path/to/file 或 /upload "/path/with spaces/file"',
@@ -161,9 +167,9 @@ def chat_with_model(
161
167
  continue
162
168
 
163
169
  # Check if it is a save command
164
- if user_input.strip().startswith("/save"):
170
+ if command == "/save":
165
171
  try:
166
- file_path = user_input.strip()[5:].strip()
172
+ file_path = args
167
173
  if not file_path:
168
174
  PrettyOutput.print(
169
175
  "请指定保存文件名,例如: /save last_message.txt",
@@ -192,9 +198,9 @@ def chat_with_model(
192
198
  continue
193
199
 
194
200
  # Check if it is a saveall command
195
- if user_input.strip().startswith("/saveall"):
201
+ if command == "/saveall":
196
202
  try:
197
- file_path = user_input.strip()[8:].strip()
203
+ file_path = args
198
204
  if not file_path:
199
205
  PrettyOutput.print(
200
206
  "请指定保存文件名,例如: /saveall all_conversations.txt",
@@ -223,9 +229,9 @@ def chat_with_model(
223
229
  continue
224
230
 
225
231
  # Check if it is a save_session command
226
- if user_input.strip().startswith("/save_session"):
232
+ if command == "/save_session":
227
233
  try:
228
- file_path = user_input.strip()[14:].strip()
234
+ file_path = args
229
235
  if not file_path:
230
236
  PrettyOutput.print(
231
237
  "请指定保存会话的文件名,例如: /save_session session.json",
@@ -250,9 +256,9 @@ def chat_with_model(
250
256
  continue
251
257
 
252
258
  # Check if it is a load_session command
253
- if user_input.strip().startswith("/load_session"):
259
+ if command == "/load_session":
254
260
  try:
255
- file_path = user_input.strip()[14:].strip()
261
+ file_path = args
256
262
  if not file_path:
257
263
  PrettyOutput.print(
258
264
  "请指定加载会话的文件名,例如: /load_session session.json",
@@ -278,18 +284,18 @@ def chat_with_model(
278
284
  continue
279
285
 
280
286
  # Check if it is a shell command
281
- if user_input.strip().startswith("/shell"):
287
+ if command == "/shell":
282
288
  try:
283
- command = user_input.strip()[6:].strip()
284
- if not command:
289
+ shell_command = args
290
+ if not shell_command:
285
291
  PrettyOutput.print(
286
292
  "请指定要执行的shell命令,例如: /shell ls -l",
287
293
  OutputType.WARNING,
288
294
  )
289
295
  continue
290
296
 
291
- PrettyOutput.print(f"执行命令: {command}", OutputType.INFO)
292
- return_code = os.system(command)
297
+ PrettyOutput.print(f"执行命令: {shell_command}", OutputType.INFO)
298
+ return_code = os.system(shell_command)
293
299
  if return_code == 0:
294
300
  PrettyOutput.print("命令执行完成", OutputType.SUCCESS)
295
301
  else:
@@ -39,4 +39,11 @@ class Tool:
39
39
  返回:
40
40
  Dict[str, Any]: 工具执行结果
41
41
  """
42
- return self.func(arguments)
42
+ try:
43
+ return self.func(arguments)
44
+ except Exception as e:
45
+ return {
46
+ "success": False,
47
+ "stderr": f"工具 {self.name} 执行失败: {str(e)}",
48
+ "stdout": "",
49
+ }
@@ -309,7 +309,9 @@ def get_modified_line_ranges() -> Dict[str, List[Tuple[int, int]]]:
309
309
  start_line = int(range_match.group(1)) # 保持从1开始
310
310
  line_count = int(range_match.group(2)) if range_match.group(2) else 1
311
311
  end_line = start_line + line_count - 1
312
- result[current_file] = (start_line, end_line)
312
+ if current_file not in result:
313
+ result[current_file] = []
314
+ result[current_file].append((start_line, end_line))
313
315
 
314
316
  return result
315
317
 
@@ -414,8 +416,23 @@ def check_and_update_git_repo(repo_path: str) -> bool:
414
416
  hasattr(sys, "base_prefix") and sys.base_prefix != sys.prefix
415
417
  )
416
418
 
417
- # 尝试普通安装
418
- install_cmd = [sys.executable, "-m", "pip", "install", "-e", "."]
419
+ is_uv_env = False
420
+ if in_venv:
421
+ # 检查是否在uv创建的虚拟环境内
422
+ if sys.platform == "win32":
423
+ uv_path = os.path.join(sys.prefix, "Scripts", "uv.exe")
424
+ else:
425
+ uv_path = os.path.join(sys.prefix, "bin", "uv")
426
+ if os.path.exists(uv_path):
427
+ is_uv_env = True
428
+
429
+ # 根据环境选择安装命令
430
+ if is_uv_env:
431
+ install_cmd = ["uv", "pip", "install", "-e", "."]
432
+ else:
433
+ install_cmd = [sys.executable, "-m", "pip", "install", "-e", "."]
434
+
435
+ # 尝试安装
419
436
  result = subprocess.run(
420
437
  install_cmd, cwd=git_root, capture_output=True, text=True
421
438
  )
@@ -236,20 +236,26 @@ def get_short_term_memories(tags: Optional[List[str]] = None) -> List[Dict[str,
236
236
  tags: 用于过滤的标签列表(可选)
237
237
 
238
238
  返回:
239
- List[Dict[str, Any]]: 符合条件的短期记忆列表
239
+ List[Dict[str, Any]]: 符合条件的短期记忆列表,按创建时间降序排列
240
240
  """
241
241
  global short_term_memories
242
- if not tags:
243
- return short_term_memories.copy()
244
242
 
245
- # 按标签过滤
246
- filtered_memories = []
247
- for memory in short_term_memories:
248
- memory_tags = memory.get("tags", [])
249
- if any(tag in memory_tags for tag in tags):
250
- filtered_memories.append(memory)
243
+ # 获取记忆副本
244
+ memories_copy = short_term_memories.copy()
245
+
246
+ # 按标签过滤(如果提供了标签)
247
+ if tags:
248
+ filtered_memories = []
249
+ for memory in memories_copy:
250
+ memory_tags = memory.get("tags", [])
251
+ if any(tag in memory_tags for tag in tags):
252
+ filtered_memories.append(memory)
253
+ memories_copy = filtered_memories
254
+
255
+ # 按创建时间排序(最新的在前)
256
+ memories_copy.sort(key=lambda x: x.get("created_at", ""), reverse=True)
251
257
 
252
- return filtered_memories
258
+ return memories_copy
253
259
 
254
260
 
255
261
  def clear_short_term_memories() -> None:
@@ -175,13 +175,20 @@ def upload_methodology(platform: BasePlatform, other_files: List[str] = []) -> b
175
175
  pass
176
176
 
177
177
 
178
- def load_methodology(user_input: str, tool_registery: Optional[Any] = None) -> str:
178
+ def load_methodology(
179
+ user_input: str,
180
+ tool_registery: Optional[Any] = None,
181
+ platform_name: Optional[str] = None,
182
+ model_name: Optional[str] = None,
183
+ ) -> str:
179
184
  """
180
185
  加载方法论并上传到大模型。
181
186
 
182
187
  参数:
183
188
  user_input: 用户输入文本,用于提示大模型
184
189
  tool_registery: 工具注册表,用于获取工具列表
190
+ platform_name (str, optional): 指定的平台名称. Defaults to None.
191
+ model_name (str, optional): 指定的模型名称. Defaults to None.
185
192
 
186
193
  返回:
187
194
  str: 相关的方法论提示,如果未找到方法论则返回空字符串
@@ -203,7 +210,17 @@ def load_methodology(user_input: str, tool_registery: Optional[Any] = None) -> s
203
210
  return ""
204
211
  print(f"✅ 加载方法论文件完成 (共 {len(methodologies)} 个)")
205
212
 
206
- platform = PlatformRegistry().get_normal_platform()
213
+ if platform_name:
214
+ platform = PlatformRegistry().create_platform(platform_name)
215
+ if platform and model_name:
216
+ platform.set_model_name(model_name)
217
+ else:
218
+ platform = PlatformRegistry().get_normal_platform()
219
+
220
+ if not platform:
221
+ PrettyOutput.print("无法创建平台实例", OutputType.ERROR)
222
+ return ""
223
+
207
224
  platform.set_suppress_output(True)
208
225
 
209
226
  # 步骤1:获取所有方法论的标题