jarvis-ai-assistant 0.1.181__py3-none-any.whl → 0.1.183__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 CHANGED
@@ -1,4 +1,4 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  """Jarvis AI Assistant"""
3
3
 
4
- __version__ = "0.1.181"
4
+ __version__ = "0.1.183"
@@ -2,7 +2,7 @@
2
2
  # 标准库导入
3
3
  import datetime
4
4
  import platform
5
- from typing import Any, Callable, List, Optional, Protocol, Tuple, Union
5
+ from typing import Any, Callable, Dict, List, Optional, Protocol, Tuple, Union
6
6
 
7
7
  # 第三方库导入
8
8
  from yaspin import yaspin # type: ignore
@@ -205,6 +205,8 @@ class Agent:
205
205
  if model_name is not None:
206
206
  self.model.set_model_name(model_name)
207
207
 
208
+ self.user_data: Dict[str, Any] = {}
209
+
208
210
  self.model.set_suppress_output(False)
209
211
 
210
212
  from jarvis.jarvis_tools.registry import ToolRegistry
@@ -319,6 +321,14 @@ class Agent:
319
321
  )
320
322
  self.first = True
321
323
 
324
+ def set_user_data(self, key: str, value: Any):
325
+ """设置用户数据"""
326
+ self.user_data[key] = value
327
+
328
+ def get_user_data(self, key: str) -> Optional[Any]:
329
+ """获取用户数据"""
330
+ return self.user_data.get(key, None)
331
+
322
332
  def set_use_tools(self, use_tools):
323
333
  """设置要使用的工具列表"""
324
334
  from jarvis.jarvis_tools.registry import ToolRegistry
@@ -372,7 +382,7 @@ class Agent:
372
382
  )
373
383
 
374
384
  addon_prompt = f"""
375
- [系统提示开始]
385
+ <system_prompt>
376
386
  请判断是否已经完成任务,如果已经完成:
377
387
  - 直接输出完成原因,不需要再有新的操作,不要输出{ot("TOOL_CALL")}标签
378
388
  {complete_prompt}
@@ -381,7 +391,7 @@ class Agent:
381
391
  - 如果信息不明确,请请求用户补充
382
392
  - 如果执行过程中连续失败5次,请使用ask_user询问用户操作
383
393
  - 操作列表:{action_handlers}
384
- [系统提示结束]
394
+ </system_prompt>
385
395
 
386
396
  请继续。
387
397
  """
@@ -0,0 +1,112 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+
4
+ import os
5
+ import re
6
+ from typing import Any, Tuple
7
+
8
+ from yaspin import yaspin
9
+
10
+ from jarvis.jarvis_tools.file_operation import FileOperationTool
11
+ from jarvis.jarvis_utils.output import OutputType, PrettyOutput
12
+ from jarvis.jarvis_utils.utils import is_context_overflow
13
+
14
+
15
+ def file_input_handler(user_input: str, agent: Any) -> Tuple[str, bool]:
16
+ """Process user input containing file references and read file contents.
17
+
18
+ Args:
19
+ user_input: Input string that may contain file references in format:
20
+ - 'file_path' (whole file)
21
+ - 'file_path:start_line,end_line' (line range)
22
+ - 'file_path:start_line:end_line' (alternative range format)
23
+ agent: Agent object for further processing (currently unused)
24
+
25
+ Returns:
26
+ Tuple[str, bool]:
27
+ - Processed prompt string with file contents prepended
28
+ - Boolean indicating if context overflow occurred
29
+ """
30
+ prompt = user_input
31
+ files = []
32
+
33
+ file_refs = re.findall(r"'([^']+)'", user_input)
34
+ for ref in file_refs:
35
+ # Handle file:start,end or file:start:end format
36
+ if ':' in ref:
37
+ file_path, line_range = ref.split(':', 1)
38
+ # Initialize with default values
39
+ start_line = 1 # 1-based
40
+ end_line = -1
41
+
42
+ # Process line range if specified
43
+ if ',' in line_range or ':' in line_range:
44
+ try:
45
+ raw_start, raw_end = map(int, re.split(r'[,:]', line_range))
46
+
47
+ # Handle special values and Python-style negative indices
48
+ try:
49
+ with open(file_path, 'r', encoding='utf-8', errors="ignore") as f:
50
+ total_lines = len(f.readlines())
51
+ except FileNotFoundError:
52
+ PrettyOutput.print(f"文件不存在: {file_path}", OutputType.WARNING)
53
+ continue
54
+ # Process start line (0 means whole file, negative means from end)
55
+ if raw_start == 0: # 0表示整个文件
56
+ start_line = 1
57
+ end_line = total_lines
58
+ else:
59
+ start_line = raw_start if raw_start > 0 else total_lines + raw_start + 1
60
+
61
+ # Process end line
62
+ if raw_end == 0: # 0表示整个文件(如果start也是0)
63
+ end_line = total_lines
64
+ else:
65
+ end_line = raw_end if raw_end > 0 else total_lines + raw_end + 1
66
+
67
+ # Auto-correct ranges
68
+ start_line = max(1, min(start_line, total_lines))
69
+ end_line = max(start_line, min(end_line, total_lines))
70
+
71
+ # Final validation
72
+ if start_line < 1 or end_line > total_lines or start_line > end_line:
73
+ raise ValueError
74
+
75
+ except:
76
+ continue
77
+
78
+ # Add file if it exists
79
+ if os.path.isfile(file_path):
80
+ files.append({
81
+ "path": file_path,
82
+ "start_line": start_line,
83
+ "end_line": end_line
84
+ })
85
+ else:
86
+ # Handle simple file path
87
+ if os.path.isfile(ref):
88
+ files.append({
89
+ "path": ref,
90
+ "start_line": 1, # 1-based
91
+ "end_line": -1
92
+ })
93
+
94
+ # Read and process files if any were found
95
+ if files:
96
+ with yaspin(text="正在读取文件...", color="cyan") as spinner:
97
+ old_prompt = prompt
98
+ result = FileOperationTool().execute({"operation":"read","files": files})
99
+ if result["success"]:
100
+ spinner.text = "文件读取完成"
101
+ spinner.ok("✅")
102
+ # Prepend file contents to prompt and check for overflow
103
+ prompt = f"""{prompt}
104
+
105
+ <file_context>
106
+ {result["stdout"]}
107
+ </file_context>"""
108
+ if is_context_overflow(prompt):
109
+ return old_prompt, False
110
+
111
+ return prompt, False
112
+
@@ -15,6 +15,7 @@ from yaspin import yaspin # type: ignore
15
15
  from jarvis import __version__
16
16
  from jarvis.jarvis_agent import Agent
17
17
  from jarvis.jarvis_agent.builtin_input_handler import builtin_input_handler
18
+ from jarvis.jarvis_agent.file_input_handler import file_input_handler
18
19
  from jarvis.jarvis_agent.shell_input_handler import shell_input_handler
19
20
  # 忽略yaspin的类型检查
20
21
  from jarvis.jarvis_code_agent.lint import get_lint_tools
@@ -108,6 +109,7 @@ class CodeAgent:
108
109
  output_handler=[tool_registry],
109
110
  platform=platform_instance,
110
111
  input_handler=[
112
+ file_input_handler,
111
113
  shell_input_handler,
112
114
  builtin_input_handler
113
115
  ],
@@ -315,7 +315,7 @@ class KimiModel(BasePlatform):
315
315
 
316
316
  def name(self) -> str:
317
317
  """Model name"""
318
- return "kimi"
318
+ return self.model_name
319
319
 
320
320
  def support_web(self) -> bool:
321
321
  """Kimi平台支持web功能"""
@@ -19,7 +19,6 @@ REQUIRED_METHODS = [
19
19
  ('set_system_message', ['message']),
20
20
  ('set_model_name', ['model_name']),
21
21
  ('get_model_list', []),
22
- ('set_suppress_output', ['suppress']),
23
22
  ('upload_files', ['file_list']),
24
23
  ]
25
24
 
@@ -0,0 +1,428 @@
1
+ # -*- coding: utf-8 -*-
2
+ import json
3
+ import os
4
+ from typing import Any, Dict, Generator, List, Tuple
5
+ import uuid
6
+
7
+ import requests
8
+
9
+ from jarvis.jarvis_platform.base import BasePlatform
10
+ from jarvis.jarvis_utils.output import PrettyOutput, OutputType
11
+ from jarvis.jarvis_utils.utils import while_success
12
+
13
+
14
+ class TongyiPlatform(BasePlatform):
15
+ """Tongyi platform implementation"""
16
+
17
+ platform_name = "tongyi"
18
+
19
+ def __init__(self):
20
+ """Initialize Tongyi platform"""
21
+ super().__init__()
22
+ self.session_id = ""
23
+ self.cookies = os.getenv("TONGYI_COOKIES", "")
24
+ self.request_id = ""
25
+ self.msg_id = ""
26
+ self.model_name = ""
27
+ self.uploaded_file_info = []
28
+
29
+
30
+ def _get_base_headers(self):
31
+ return {
32
+ "Host": "api.tongyi.com",
33
+ "Connection": "keep-alive",
34
+ "X-Platform": "pc_tongyi",
35
+ "sec-ch-ua-platform": "Windows",
36
+ "sec-ch-ua": '"Chromium";v="136", "Microsoft Edge";v="136", "Not.A/Brand";v="99"',
37
+ "sec-ch-ua-mobile": "?0",
38
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36 Edg/136.0.0.0",
39
+ "accept": "application/json, text/plain, */*",
40
+ "DNT": "1",
41
+ "Content-Type": "application/json",
42
+ "Origin": "https://www.tongyi.com",
43
+ "Sec-Fetch-Site": "same-site",
44
+ "Sec-Fetch-Mode": "cors",
45
+ "Sec-Fetch-Dest": "empty",
46
+ "Referer": "https://www.tongyi.com/qianwen",
47
+ "Accept-Encoding": "gzip, deflate, br, zstd",
48
+ "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
49
+ "Cookie": self.cookies
50
+ }
51
+
52
+ def set_model_name(self, model_name: str):
53
+ """Set model name
54
+
55
+ Args:
56
+ model_name: Model name to use
57
+ """
58
+ self.model_name = model_name
59
+
60
+ def _generate_request_id(self):
61
+ self.request_id = str(uuid.uuid4()).replace("-", "")
62
+
63
+ def chat(self, message: str) -> Generator[str, None, None]:
64
+ if not self.request_id:
65
+ self._generate_request_id()
66
+ url = "https://api.tongyi.com/dialog/conversation"
67
+ headers = self._get_base_headers()
68
+
69
+ headers["accept"] = "text/event-stream"
70
+
71
+ # Prepare contents array with message
72
+ contents = [{
73
+ "content": message,
74
+ "contentType": "text",
75
+ "role": "user",
76
+ "ext": {
77
+ "searchType": "",
78
+ "pptGenerate": False,
79
+ "deepThink": False,
80
+ "deepResearch": False
81
+ }
82
+ }]
83
+
84
+ # Add uploaded files to contents if available and clear after use
85
+ if self.uploaded_file_info:
86
+ for file_info in self.uploaded_file_info:
87
+ contents.append({
88
+ "role": "user",
89
+ "contentType": "file",
90
+ "content": file_info["url"],
91
+ "ext": {
92
+ "fileSize": file_info.get("fileSize", 0),
93
+ "batchId": file_info.get("batchId", ""),
94
+ "docId": file_info.get("docId", "")
95
+ }
96
+ })
97
+ # Clear uploaded file info after using it
98
+ self.uploaded_file_info = []
99
+
100
+ payload = {
101
+ "model": "",
102
+ "action": "next",
103
+ "mode": "chat",
104
+ "userAction": "new_top",
105
+ "requestId": self.request_id,
106
+ "sessionId": self.session_id,
107
+ "sessionType": "text_chat",
108
+ "parentMsgId": self.msg_id,
109
+ "params": {
110
+ "agentId": "",
111
+ "searchType": "",
112
+ "pptGenerate": False,
113
+ "bizScene": "code_chat" if self.model_name == "Code-Chat" else "",
114
+ "bizSceneInfo": {},
115
+ "specifiedModel": "",
116
+ "deepThink": True if self.model_name == "Thinking" else False,
117
+ "deepResearch": False,
118
+ "fileUploadBatchId": self.uploaded_file_info[0]["batchId"] if self.uploaded_file_info else ""
119
+ },
120
+ "contents": contents
121
+ }
122
+
123
+ try:
124
+ response = while_success(lambda: requests.post(url, headers=headers, json=payload, stream=True), sleep_time=5)
125
+ if response.status_code != 200:
126
+ raise Exception(f"HTTP {response.status_code}: {response.text}")
127
+ msg_id = ""
128
+ session_id = ""
129
+ thinking_content = ""
130
+ text_content = ""
131
+ in_thinking = False
132
+ for line in response.iter_lines():
133
+ if not line:
134
+ continue
135
+ line_str = line.decode('utf-8')
136
+ if not line_str.startswith("data: "):
137
+ continue
138
+
139
+ try:
140
+ data = json.loads(line_str[6:])
141
+ # 记录消息ID和会话ID
142
+ if "msgId" in data:
143
+ msg_id = data["msgId"]
144
+ if "sessionId" in data:
145
+ session_id = data["sessionId"]
146
+
147
+ if "contents" in data and len(data["contents"]) > 0:
148
+ for content in data["contents"]:
149
+ if content.get("contentType") == "think":
150
+ if not in_thinking:
151
+ yield "<think>\n\n"
152
+ in_thinking = True
153
+ if content.get("incremental"):
154
+ tmp_content = json.loads(content.get("content"))["content"]
155
+ thinking_content += tmp_content
156
+ yield tmp_content
157
+ else:
158
+ tmp_content = json.loads(content.get("content"))["content"]
159
+ if len(thinking_content) < len(tmp_content):
160
+ yield tmp_content[len(thinking_content):]
161
+ thinking_content = tmp_content
162
+ else:
163
+ # thinking_content = "aaa</thi"
164
+ # tmp_content = "aaa"
165
+ # 应该yield nk>
166
+ # print("\n")
167
+ # print(len(thinking_content))
168
+ # print(len(tmp_content))
169
+ # print("--------------------------------")
170
+ # print(thinking_content)
171
+ # print("--------------------------------")
172
+ # print(tmp_content)
173
+ # print("--------------------------------")
174
+ yield "\r\n</think>\n"[len(thinking_content)-len(tmp_content):]
175
+ thinking_content = tmp_content
176
+ in_thinking = False
177
+ elif content.get("contentType") == "text":
178
+ if in_thinking:
179
+ continue
180
+ if content.get("incremental"):
181
+ tmp_content = content.get("content")
182
+ text_content += tmp_content
183
+ yield tmp_content
184
+ else:
185
+ tmp_content = content.get("content")
186
+ if len(text_content) < len(tmp_content):
187
+ yield tmp_content[len(text_content):]
188
+ text_content = tmp_content
189
+
190
+
191
+ except json.JSONDecodeError:
192
+ continue
193
+
194
+ self.msg_id = msg_id
195
+ self.session_id = session_id
196
+
197
+ return None
198
+
199
+ except Exception as e:
200
+ raise Exception(f"Chat failed: {str(e)}")
201
+
202
+ def _get_upload_token(self) -> Dict[str, Any]:
203
+ """Get upload token from Tongyi API
204
+
205
+ Returns:
206
+ Dict[str, Any]: Upload token information including accessId, bucketName, etc.
207
+ """
208
+ url = "https://api.tongyi.com/dialog/uploadToken"
209
+ headers = self._get_base_headers()
210
+ payload = {}
211
+
212
+ try:
213
+ response = while_success(lambda: requests.post(url, headers=headers, json=payload), sleep_time=5)
214
+ if response.status_code != 200:
215
+ raise Exception(f"HTTP {response.status_code}: {response.text}")
216
+
217
+ result = response.json()
218
+ if not result.get("success"):
219
+ raise Exception(f"Failed to get upload token: {result.get('errorMsg')}")
220
+
221
+ return result.get("data", {})
222
+
223
+ except Exception as e:
224
+ raise Exception(f"Failed to get upload token: {str(e)}")
225
+
226
+
227
+ def upload_files(self, file_list: List[str]) -> bool:
228
+ """Upload files to Tongyi platform and get download links
229
+
230
+ Args:
231
+ file_list: List of file paths to upload
232
+
233
+ Returns:
234
+ List[Dict[str, str]]: List of dictionaries containing file info and download URLs
235
+ """
236
+ try:
237
+ upload_token = self._get_upload_token()
238
+ uploaded_files = []
239
+
240
+ for file_path in file_list:
241
+ if not os.path.exists(file_path):
242
+ PrettyOutput.print(f"File not found: {file_path}", OutputType.ERROR)
243
+ return False
244
+
245
+ # Get file name and content type
246
+ file_name = os.path.basename(file_path)
247
+ content_type = self._get_content_type(file_path)
248
+
249
+ # Prepare form data
250
+ form_data = {
251
+ 'OSSAccessKeyId': upload_token['accessId'],
252
+ 'policy': upload_token['policy'],
253
+ 'signature': upload_token['signature'],
254
+ 'key': f"{upload_token['dir']}{file_name}",
255
+ 'dir': upload_token['dir'],
256
+ 'success_action_status': '200'
257
+ }
258
+
259
+ # Prepare files
260
+ files = {
261
+ 'file': (file_name, open(file_path, 'rb'), content_type)
262
+ }
263
+
264
+ # Upload file
265
+ response = requests.post(
266
+ upload_token['host'],
267
+ data=form_data,
268
+ files=files
269
+ )
270
+
271
+ if response.status_code != 200:
272
+ PrettyOutput.print(f"Failed to upload {file_name}: HTTP {response.status_code}", OutputType.ERROR)
273
+ return False
274
+
275
+ uploaded_files.append({
276
+ 'fileKey': file_name,
277
+ 'fileType': 'file',
278
+ 'dir': upload_token['dir']
279
+ })
280
+
281
+ # Get download links for uploaded files
282
+ url = "https://api.tongyi.com/dialog/downloadLink/batch"
283
+ headers = self._get_base_headers()
284
+ payload = {
285
+ "fileKeys": [f['fileKey'] for f in uploaded_files],
286
+ "fileType": "file",
287
+ "dir": upload_token['dir']
288
+ }
289
+
290
+ response = requests.post(url, headers=headers, json=payload)
291
+ if response.status_code != 200:
292
+ PrettyOutput.print(f"Failed to get download links: HTTP {response.status_code}", OutputType.ERROR)
293
+ return False
294
+
295
+ result = response.json()
296
+ if not result.get("success"):
297
+ PrettyOutput.print(f"Failed to get download links: {result.get('errorMsg')}", OutputType.ERROR)
298
+ return False
299
+
300
+ # Add files to chat
301
+ self.uploaded_file_info = result.get("data", {}).get("results", [])
302
+ for file_info in self.uploaded_file_info:
303
+ add_url = "https://api.tongyi.com/assistant/api/chat/file/add"
304
+ add_payload = {
305
+ "workSource": "chat",
306
+ "terminal": "web",
307
+ "workCode": "0",
308
+ "channel": "home",
309
+ "workType": "file",
310
+ "module": "uploadhistory",
311
+ "workName": file_info["fileKey"],
312
+ "workId": file_info["docId"],
313
+ "workResourcePath": file_info["url"],
314
+ "sessionId": "",
315
+ "batchId": str(uuid.uuid4()).replace('-', '')[:32], # Generate random batchId
316
+ "fileSize": os.path.getsize(file_path)
317
+ }
318
+
319
+ add_response = requests.post(add_url, headers=headers, json=add_payload)
320
+ if add_response.status_code != 200:
321
+ PrettyOutput.print(f"Failed to add file to chat: HTTP {add_response.status_code}", OutputType.ERROR)
322
+ continue
323
+
324
+ add_result = add_response.json()
325
+ if not add_result.get("success"):
326
+ PrettyOutput.print(f"Failed to add file to chat: {add_result.get('errorMsg')}", OutputType.ERROR)
327
+ continue
328
+
329
+ file_info.update(add_result.get("data", {}))
330
+
331
+ return True
332
+
333
+ except Exception as e:
334
+ PrettyOutput.print(f"Error uploading files: {str(e)}", OutputType.ERROR)
335
+ return False
336
+
337
+ def _get_content_type(self, file_path: str) -> str:
338
+ """Get content type for file
339
+
340
+ Args:
341
+ file_path: Path to file
342
+
343
+ Returns:
344
+ str: Content type
345
+ """
346
+ ext = os.path.splitext(file_path)[1].lower()
347
+ content_types = {
348
+ '.txt': 'text/plain',
349
+ '.md': 'text/markdown',
350
+ '.doc': 'application/msword',
351
+ '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
352
+ '.xls': 'application/vnd.ms-excel',
353
+ '.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
354
+ '.pdf': 'application/pdf',
355
+ '.png': 'image/png',
356
+ '.jpg': 'image/jpeg',
357
+ '.jpeg': 'image/jpeg',
358
+ '.gif': 'image/gif',
359
+ '.mp4': 'video/mp4',
360
+ '.mp3': 'audio/mpeg',
361
+ '.wav': 'audio/wav'
362
+ }
363
+ return content_types.get(ext, 'application/octet-stream')
364
+
365
+ def name(self) -> str:
366
+ """Get platform name
367
+
368
+ Returns:
369
+ str: Platform name
370
+ """
371
+ return self.model_name
372
+
373
+ def delete_chat(self) -> bool:
374
+ """Delete chat history
375
+
376
+ Returns:
377
+ bool: True if deletion successful, False otherwise
378
+ """
379
+ if not self.session_id:
380
+ return True
381
+
382
+ url = "https://api.tongyi.com/dialog/session/delete"
383
+ headers = self._get_base_headers()
384
+ payload = {
385
+ "sessionId": self.session_id
386
+ }
387
+
388
+ try:
389
+ response = while_success(lambda: requests.post(url, headers=headers, json=payload), sleep_time=5)
390
+ if response.status_code != 200:
391
+ PrettyOutput.print(f"Failed to delete chat: HTTP {response.status_code}", OutputType.ERROR)
392
+ return False
393
+ self.request_id = ""
394
+ self.session_id = ""
395
+ self.msg_id = ""
396
+ return True
397
+ except Exception as e:
398
+ PrettyOutput.print(f"Error deleting chat: {str(e)}", OutputType.ERROR)
399
+ return False
400
+
401
+ def set_system_message(self, message: str):
402
+ """Set system message
403
+
404
+ Args:
405
+ message: System message to set
406
+ """
407
+ self.system_message = message
408
+
409
+ def get_model_list(self) -> List[Tuple[str, str]]:
410
+ """Get available model list
411
+
412
+ Returns:
413
+ List[Tuple[str, str]]: List of (model_id, model_name) tuples
414
+ """
415
+ return [
416
+ ("Normal", "Normal"),
417
+ ("Thinking", "Thinking"),
418
+ ("Deep-Research", "Deep-Research"),
419
+ ("Code-Chat", "Code-Chat"),
420
+ ]
421
+
422
+ def support_web(self) -> bool:
423
+ """Check if platform supports web functionality
424
+
425
+ Returns:
426
+ bool: True if web is supported, False otherwise
427
+ """
428
+ return True
@@ -430,6 +430,8 @@ class YuanbaoPlatform(BasePlatform):
430
430
  if hasattr(response, 'text'):
431
431
  error_msg += f", 响应: {response.text}"
432
432
  raise Exception(error_msg)
433
+
434
+ in_thinking = False
433
435
 
434
436
  # 处理SSE流响应
435
437
  for line in response.iter_lines():
@@ -446,12 +448,18 @@ class YuanbaoPlatform(BasePlatform):
446
448
 
447
449
  # 处理文本类型的消息
448
450
  if data.get("type") == "text":
451
+ if in_thinking:
452
+ yield "</think>\n"
453
+ in_thinking = False
449
454
  msg = data.get("msg", "")
450
455
  if msg:
451
456
  yield msg
452
457
 
453
458
  # 处理思考中的消息
454
459
  elif data.get("type") == "think":
460
+ if not in_thinking:
461
+ yield "<think>\n"
462
+ in_thinking = True
455
463
  think_content = data.get("content", "")
456
464
  if think_content:
457
465
  yield think_content
@@ -138,6 +138,7 @@ class FileSearchReplaceTool:
138
138
 
139
139
  file_path = args["file"]
140
140
  changes = args["changes"]
141
+ agent = args.get("agent", None)
141
142
 
142
143
  # 创建已处理文件变量,用于失败时回滚
143
144
  original_content = None
@@ -153,6 +154,17 @@ class FileSearchReplaceTool:
153
154
  with open(file_path, 'r', encoding='utf-8') as f:
154
155
  content = f.read()
155
156
  original_content = content
157
+
158
+ if file_exists and agent:
159
+ files = agent.get_user_data("files")
160
+ if not files or files.get(file_path, None) is None:
161
+ return {
162
+ "success": False,
163
+ "stdout": "",
164
+ "stderr": f"请先读取文件 {file_path} 的内容后再编辑"
165
+ }
166
+
167
+
156
168
  with yaspin(text=f"正在处理文件 {file_path}...", color="cyan") as spinner:
157
169
  success, temp_content = fast_edit(file_path, changes, spinner)
158
170
  if not success:
@@ -50,7 +50,7 @@ class FileOperationTool:
50
50
  return None # 如果没有合适的处理器,返回None
51
51
 
52
52
  def _handle_single_file(self, operation: str, filepath: str, content: str = "",
53
- start_line: int = 1, end_line: int = -1) -> Dict[str, Any]:
53
+ start_line: int = 1, end_line: int = -1, agent: Any = None) -> Dict[str, Any]:
54
54
  """Handle operations for a single file"""
55
55
  try:
56
56
  abs_path = os.path.abspath(filepath)
@@ -128,6 +128,15 @@ class FileOperationTool:
128
128
 
129
129
  spinner.text = f"文件读取完成: {abs_path}"
130
130
  spinner.ok("✅")
131
+
132
+ if agent:
133
+ files = agent.get_user_data("files")
134
+ if files:
135
+ files.append(abs_path)
136
+ else:
137
+ files = [abs_path]
138
+ agent.set_user_data("files", files)
139
+
131
140
  return {
132
141
  "success": True,
133
142
  "stdout": output,
@@ -173,7 +182,7 @@ class FileOperationTool:
173
182
  """
174
183
  try:
175
184
  operation = args["operation"].strip()
176
-
185
+ agent = args.get("agent", None)
177
186
  if "files" not in args or not isinstance(args["files"], list):
178
187
  return {
179
188
  "success": False,
@@ -194,7 +203,8 @@ class FileOperationTool:
194
203
  file_info["path"].strip(),
195
204
  content,
196
205
  file_info.get("start_line", 1),
197
- file_info.get("end_line", -1)
206
+ file_info.get("end_line", -1),
207
+ agent
198
208
  )
199
209
 
200
210
  if result["success"]:
@@ -31,7 +31,7 @@ class ReadCodeTool:
31
31
  "required": ["files"]
32
32
  }
33
33
 
34
- def _handle_single_file(self, filepath: str, start_line: int = 1, end_line: int = -1) -> Dict[str, Any]:
34
+ def _handle_single_file(self, filepath: str, start_line: int = 1, end_line: int = -1, agent: Any = None) -> Dict[str, Any]:
35
35
  """处理单个文件的读取操作
36
36
 
37
37
  Args:
@@ -99,6 +99,14 @@ class ReadCodeTool:
99
99
  spinner.text = f"文件读取完成: {abs_path}"
100
100
  spinner.ok("✅")
101
101
 
102
+ if agent:
103
+ files = agent.get_user_data("files")
104
+ if files:
105
+ files.append(abs_path)
106
+ else:
107
+ files = [abs_path]
108
+ agent.set_user_data("files", files)
109
+
102
110
  return {
103
111
  "success": True,
104
112
  "stdout": output,
@@ -123,6 +131,7 @@ class ReadCodeTool:
123
131
  Dict[str, Any]: 包含成功状态、输出内容和错误信息的字典
124
132
  """
125
133
  try:
134
+ agent = args.get("agent", None)
126
135
  if "files" not in args or not isinstance(args["files"], list):
127
136
  return {
128
137
  "success": False,
@@ -140,7 +149,8 @@ class ReadCodeTool:
140
149
  result = self._handle_single_file(
141
150
  file_info["path"].strip(),
142
151
  file_info.get("start_line", 1),
143
- file_info.get("end_line", -1)
152
+ file_info.get("end_line", -1),
153
+ agent
144
154
  )
145
155
 
146
156
  if result["success"]:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jarvis-ai-assistant
3
- Version: 0.1.181
3
+ Version: 0.1.183
4
4
  Summary: Jarvis: An AI assistant that uses tools to interact with the system
5
5
  Home-page: https://github.com/skyfireitdiy/Jarvis
6
6
  Author: skyfire
@@ -153,6 +153,35 @@ Kimi API Key获取方式:
153
153
  删除Bearer前缀,剩下的内容就是Kimi API Key。
154
154
 
155
155
 
156
+ #### 通义千问
157
+ ```yaml
158
+ JARVIS_PLATFORM: tongyi
159
+ JARVIS_MODEL: Normal # 可选模型:Normal, Thinking, Deep-Research, Code-Chat
160
+ JARVIS_THINKING_PLATFORM: tongyi
161
+ JARVIS_THINKING_MODEL: Thinking
162
+
163
+ ENV:
164
+ TONGYI_COOKIES: <通义千问cookies>
165
+ ```
166
+
167
+ 通义千问cookies获取方式:
168
+
169
+ ![通义千问cookies获取方式](docs/images/tongyi.png)
170
+
171
+ 1. 登录[通义千问](https://www.tongyi.com/qianwen)
172
+ 2. 打开浏览器开发者工具(F12)
173
+ 3. 在Network标签页中找到任意请求
174
+ 4. 在请求头中找到Cookie字段,复制其值
175
+
176
+ 配置说明:
177
+ 1. `TONGYI_COOKIES`: 必填,用于身份验证
178
+ 2. 支持的模型:
179
+ - `Normal`: 标准对话模型
180
+ - `Thinking`: 深度思考模型
181
+ - `Deep-Research`: 深度研究模型
182
+ - `Code-Chat`: 代码对话模型
183
+
184
+
156
185
  #### OpenAI
157
186
  ```yaml
158
187
  JARVIS_PLATFORM: openai
@@ -1,12 +1,13 @@
1
- jarvis/__init__.py,sha256=dgQhg6Gd7pGul0ij7e_dHnY6CjyGHRhshKt8hag08qo,74
2
- jarvis/jarvis_agent/__init__.py,sha256=9vyME5JbA4koVoqJ_zv5N9eNdvu6iBkB4o4a1CmkVgA,30054
1
+ jarvis/__init__.py,sha256=KysQNyO0AUjUMLzRISa3Wf1q9ffBDepVKiOh2ztirYg,74
2
+ jarvis/jarvis_agent/__init__.py,sha256=AxT_2n-IQkbtoQlAS3SJ0tsvcUenWD7_Xrc-RZZCWiA,30352
3
3
  jarvis/jarvis_agent/builtin_input_handler.py,sha256=f4DaEHPakXcAbgykFP-tiOQP6fh_yGFlZx_h91_j2tQ,1529
4
+ jarvis/jarvis_agent/file_input_handler.py,sha256=LDNXoTtyjhyBmfzDnAdbWZ2BWdu4q-r6thSKRK8Iwjk,4187
4
5
  jarvis/jarvis_agent/jarvis.py,sha256=UkNMVUlSNKV6y3v12eAhqc_gIDB6Obxrwk5f7-sQeiQ,6137
5
6
  jarvis/jarvis_agent/main.py,sha256=GkjMTIbsd56nkVuRwD_tU_PZWyzixZZhMjVOCd0SzOA,2669
6
7
  jarvis/jarvis_agent/output_handler.py,sha256=7qori-RGrQmdiFepoEe3oPPKJIvRt90l_JDmvCoa4zA,1219
7
8
  jarvis/jarvis_agent/shell_input_handler.py,sha256=pi3AtPKrkKc6K9e99S1djKXQ_XrxtP6FrSWebQmRT6E,1261
8
9
  jarvis/jarvis_code_agent/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
- jarvis/jarvis_code_agent/code_agent.py,sha256=IHUDqZ2RvJFoYIUuJAVCsbEOtYPHwXLfrmPXnpxKjvU,15721
10
+ jarvis/jarvis_code_agent/code_agent.py,sha256=2TG_Hi_2mbiZHUcWnxYs4C30eYpgSedtIYZLs7BbyUk,15827
10
11
  jarvis/jarvis_code_agent/lint.py,sha256=TZlhNbeaoLzO9DzExjN5GAjrt66owd8lyQV56LTfkrs,4370
11
12
  jarvis/jarvis_code_analysis/code_review.py,sha256=SEK5NOGDWgMNX3zmRMWrIOtq3Xr8cKt_asG51U8h6SA,30219
12
13
  jarvis/jarvis_code_analysis/checklists/__init__.py,sha256=cKQ_FOGy5TQgM-YkRCqORo-mUOZaPAJ9VDmZoFX58us,78
@@ -48,10 +49,11 @@ jarvis/jarvis_multi_agent/main.py,sha256=KeGv8sdpSgTjW6VE4-tQ8BWDC_a0aE_4R3OqzPB
48
49
  jarvis/jarvis_platform/__init__.py,sha256=0YnsUoM4JkIBOtImFdjfuDbrqQZT3dEaAwSJ62DrpCc,104
49
50
  jarvis/jarvis_platform/base.py,sha256=HbE7BVh8F5F38rr9K9281h6Q11XyWgDGzyPXe-e_Th0,7086
50
51
  jarvis/jarvis_platform/human.py,sha256=xwaTZ1zdrAYZZFXxkbHvUdECwCGsic0kgAFUncUr45g,2567
51
- jarvis/jarvis_platform/kimi.py,sha256=k0dYwuRf-snmJF206D7inahUcZUZG0VqOmhphj09NzQ,11969
52
+ jarvis/jarvis_platform/kimi.py,sha256=b3EpnmHseZwrfCc8sMmvwLJ6Jg2FWf8ATItSDz5G3eQ,11978
52
53
  jarvis/jarvis_platform/openai.py,sha256=VyX3bR1rGxrJdWOtUBf8PgSL9n06KaNbOewL1urzOnk,4741
53
- jarvis/jarvis_platform/registry.py,sha256=CxAELjDrc-KKPPKdP71E_qaFisfQztvwc_tdf3WpOt8,7863
54
- jarvis/jarvis_platform/yuanbao.py,sha256=vKb6oy5cTMQCwqcqpaVur7BFtQwX1Cv-mYnswP-L4mA,20291
54
+ jarvis/jarvis_platform/registry.py,sha256=3djxE8AB4gwrdAOvRSL0612Rt_CcsaZhzZ0_oXHu6xk,7820
55
+ jarvis/jarvis_platform/tongyi.py,sha256=m44aZHZ1oCbYdlSMuG3qYPFZbHW4e3VlaFZ2i3H7xrE,16927
56
+ jarvis/jarvis_platform/yuanbao.py,sha256=FDi-D9Jnw_MiwI0skPNMYz874o6GhWhdNRdZg-ECoUA,20632
55
57
  jarvis/jarvis_platform_manager/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
56
58
  jarvis/jarvis_platform_manager/main.py,sha256=OXWj18SqiV0Gl75YT6D9wspCCB4Nes04EY-ShI9kbpU,25677
57
59
  jarvis/jarvis_smart_shell/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -63,13 +65,13 @@ jarvis/jarvis_tools/chdir.py,sha256=DNKVFrWqu6t_sZ2ipv99s6802QR4cSGlqKlmaI--arE,
63
65
  jarvis/jarvis_tools/code_plan.py,sha256=gWR0lzY62x2PxWKoMRBqW6jq7zQuO8vhpjC4TcHSYjk,7685
64
66
  jarvis/jarvis_tools/create_code_agent.py,sha256=-nHfo5O5pDIG5IX3w1ClQafGvGcdI2_w75-KGrD-gUQ,3458
65
67
  jarvis/jarvis_tools/create_sub_agent.py,sha256=lyFrrg4V0yXULmU3vldwGp_euZjwZzJcRU6mJ20zejY,3023
66
- jarvis/jarvis_tools/edit_file.py,sha256=OiTP50yaUE-WTSKnp8axo96OfddHufWKpGuEa6YRG30,16269
68
+ jarvis/jarvis_tools/edit_file.py,sha256=gxnVijz-mOHpb9A7WTPIqCwmZHInSHwu_Psa_GvNWRQ,16724
67
69
  jarvis/jarvis_tools/execute_script.py,sha256=IA1SkcnwBB9PKG2voBNx5N9GXL303OC7OOtdqRfqWOk,6428
68
70
  jarvis/jarvis_tools/file_analyzer.py,sha256=7ILHkUFm8pPZn1y_s4uT0kaWHP-EmlHnpkovDdA1yRE,4872
69
- jarvis/jarvis_tools/file_operation.py,sha256=RcOKuMFUv01tvKoiOfu1ERCjvDVfJBvkT4oBpq-8vNQ,9036
71
+ jarvis/jarvis_tools/file_operation.py,sha256=WloC1-oPJLwgICu4WBc9f7XA8N_Ggl73QQ5CxM2XTlE,9464
70
72
  jarvis/jarvis_tools/generate_new_tool.py,sha256=k1Vt88kI1bYi1OwxvJqFKr3Ewwwv7lOegYNmZ-1F7x0,10283
71
73
  jarvis/jarvis_tools/methodology.py,sha256=m7cQmVhhQpUUl_uYTVvcW0JBovQLx5pWTXh_8K77HsU,5237
72
- jarvis/jarvis_tools/read_code.py,sha256=j4niDMOAKW_3rVxmOo3wxYBQ2T8vkxqEiTkBt28gUT8,5898
74
+ jarvis/jarvis_tools/read_code.py,sha256=pL2SwZDsJbJMXo4stW96quFsLgbtPVIAW-h4sDKsLtM,6274
73
75
  jarvis/jarvis_tools/read_webpage.py,sha256=PFAYuKjay9j6phWzyuZ99ZfNaHJljmRWAgS0bsvbcvE,2219
74
76
  jarvis/jarvis_tools/registry.py,sha256=WvYPiaUrleFqeXvwRkxM-6TNs1sWm61mpg1MFVo_kas,25113
75
77
  jarvis/jarvis_tools/rewrite_file.py,sha256=3V2l7kG5DG9iRimBce-1qCRuJPL0QM32SBTzOl2zCqM,7004
@@ -89,9 +91,9 @@ jarvis/jarvis_utils/methodology.py,sha256=A8pE8ZqNHvGKaDO4TFtg7Oz-hAXPBcQfhmSPWM
89
91
  jarvis/jarvis_utils/output.py,sha256=QboL42GtG_dnvd1O64sl8o72mEBhXNRADPXQMXgDE7Q,9661
90
92
  jarvis/jarvis_utils/tag.py,sha256=YJHmuedLb7_AiqvKQetHr4R1FxyzIh7HN0RRkWMmYbU,429
91
93
  jarvis/jarvis_utils/utils.py,sha256=atSK-2cUr7_tOIFsQzJnuQxebi7aFN4jtmaoXEaV4jM,10692
92
- jarvis_ai_assistant-0.1.181.dist-info/licenses/LICENSE,sha256=AGgVgQmTqFvaztRtCAXsAMryUymB18gZif7_l2e1XOg,1063
93
- jarvis_ai_assistant-0.1.181.dist-info/METADATA,sha256=0rpJwI0H5WiJsTHHQgmWeyeM7P4RZ7ei_L-Tnzt5ktI,15059
94
- jarvis_ai_assistant-0.1.181.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
95
- jarvis_ai_assistant-0.1.181.dist-info/entry_points.txt,sha256=Gy3DOP1PYLMK0GCj4rrP_9lkOyBQ39EK_lKGUSwn41E,869
96
- jarvis_ai_assistant-0.1.181.dist-info/top_level.txt,sha256=1BOxyWfzOP_ZXj8rVTDnNCJ92bBGB0rwq8N1PCpoMIs,7
97
- jarvis_ai_assistant-0.1.181.dist-info/RECORD,,
94
+ jarvis_ai_assistant-0.1.183.dist-info/licenses/LICENSE,sha256=AGgVgQmTqFvaztRtCAXsAMryUymB18gZif7_l2e1XOg,1063
95
+ jarvis_ai_assistant-0.1.183.dist-info/METADATA,sha256=RRkBRiDEeBnEfQQFAufn9jE2RV7BA90P5o5G_PnJUp0,15836
96
+ jarvis_ai_assistant-0.1.183.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
97
+ jarvis_ai_assistant-0.1.183.dist-info/entry_points.txt,sha256=Gy3DOP1PYLMK0GCj4rrP_9lkOyBQ39EK_lKGUSwn41E,869
98
+ jarvis_ai_assistant-0.1.183.dist-info/top_level.txt,sha256=1BOxyWfzOP_ZXj8rVTDnNCJ92bBGB0rwq8N1PCpoMIs,7
99
+ jarvis_ai_assistant-0.1.183.dist-info/RECORD,,