jarvis-ai-assistant 0.1.101__py3-none-any.whl → 0.1.102__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 +1 -1
- jarvis/agent.py +3 -24
- jarvis/jarvis_code_agent/main.py +1 -3
- jarvis/jarvis_github/main.py +232 -0
- jarvis/models/ai8.py +2 -3
- jarvis/models/oyi.py +1 -3
- jarvis/tools/registry.py +48 -40
- jarvis/utils.py +9 -124
- {jarvis_ai_assistant-0.1.101.dist-info → jarvis_ai_assistant-0.1.102.dist-info}/METADATA +1 -47
- {jarvis_ai_assistant-0.1.101.dist-info → jarvis_ai_assistant-0.1.102.dist-info}/RECORD +15 -20
- {jarvis_ai_assistant-0.1.101.dist-info → jarvis_ai_assistant-0.1.102.dist-info}/entry_points.txt +0 -3
- jarvis/jarvis_codebase/main.py +0 -875
- jarvis/jarvis_coder/main.py +0 -241
- jarvis/jarvis_coder/plan_generator.py +0 -145
- jarvis/jarvis_rag/__init__.py +0 -0
- jarvis/jarvis_rag/main.py +0 -822
- jarvis/tools/rag.py +0 -138
- /jarvis/{jarvis_codebase → jarvis_github}/__init__.py +0 -0
- {jarvis_ai_assistant-0.1.101.dist-info → jarvis_ai_assistant-0.1.102.dist-info}/LICENSE +0 -0
- {jarvis_ai_assistant-0.1.101.dist-info → jarvis_ai_assistant-0.1.102.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.1.101.dist-info → jarvis_ai_assistant-0.1.102.dist-info}/top_level.txt +0 -0
jarvis/jarvis_coder/main.py
DELETED
|
@@ -1,241 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import threading
|
|
3
|
-
from typing import Dict, Any, List, Optional
|
|
4
|
-
import re
|
|
5
|
-
|
|
6
|
-
from jarvis.jarvis_coder.file_select import select_files
|
|
7
|
-
from jarvis.utils import OutputType, PrettyOutput, find_git_root, get_max_context_length, is_long_context, init_env, while_success
|
|
8
|
-
from jarvis.models.registry import PlatformRegistry
|
|
9
|
-
from jarvis.jarvis_codebase.main import CodeBase
|
|
10
|
-
from prompt_toolkit import PromptSession
|
|
11
|
-
from prompt_toolkit.completion import WordCompleter, Completer, Completion
|
|
12
|
-
from prompt_toolkit.formatted_text import FormattedText
|
|
13
|
-
from prompt_toolkit.styles import Style
|
|
14
|
-
import fnmatch
|
|
15
|
-
from .patch_handler import PatchHandler
|
|
16
|
-
from .git_utils import generate_commit_message, init_git_repo, save_edit_record
|
|
17
|
-
from .plan_generator import PlanGenerator
|
|
18
|
-
|
|
19
|
-
# 全局锁对象
|
|
20
|
-
index_lock = threading.Lock()
|
|
21
|
-
|
|
22
|
-
class JarvisCoder:
|
|
23
|
-
def __init__(self, root_dir: str, language: Optional[str] = "python"):
|
|
24
|
-
"""Initialize code modification tool"""
|
|
25
|
-
self.root_dir = root_dir
|
|
26
|
-
self.language = language
|
|
27
|
-
self._init_directories()
|
|
28
|
-
self._init_codebase()
|
|
29
|
-
|
|
30
|
-
def _init_directories(self):
|
|
31
|
-
"""Initialize directories"""
|
|
32
|
-
self.max_context_length = get_max_context_length()
|
|
33
|
-
self.root_dir = init_git_repo(self.root_dir)
|
|
34
|
-
|
|
35
|
-
def _init_codebase(self):
|
|
36
|
-
"""Initialize codebase"""
|
|
37
|
-
self._codebase = CodeBase(self.root_dir)
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
def _load_related_files(self, feature: str) -> List[str]:
|
|
41
|
-
"""Load related file content"""
|
|
42
|
-
ret = []
|
|
43
|
-
# Ensure the index database is generated
|
|
44
|
-
if not self._codebase.is_index_generated():
|
|
45
|
-
PrettyOutput.print("Index database not generated, generating...", OutputType.WARNING)
|
|
46
|
-
self._codebase.generate_codebase()
|
|
47
|
-
|
|
48
|
-
related_files = self._codebase.search_similar(feature)
|
|
49
|
-
for file in related_files:
|
|
50
|
-
PrettyOutput.print(f"Related file: {file}", OutputType.SUCCESS)
|
|
51
|
-
ret.append(file)
|
|
52
|
-
return ret
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
def execute(self, feature: str) -> Dict[str, Any]:
|
|
57
|
-
"""Execute code modification"""
|
|
58
|
-
try:
|
|
59
|
-
# Get and select related files
|
|
60
|
-
initial_files = self._load_related_files(feature)
|
|
61
|
-
selected_files = select_files(initial_files, self.root_dir)
|
|
62
|
-
|
|
63
|
-
# Get modification plan
|
|
64
|
-
structed_plan = PlanGenerator().generate_plan(feature, selected_files)
|
|
65
|
-
if not structed_plan:
|
|
66
|
-
return {
|
|
67
|
-
"success": False,
|
|
68
|
-
"stdout": "",
|
|
69
|
-
"stderr": "Failed to generate modification plan, please modify the requirement and try again",
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
# Execute modification
|
|
73
|
-
if PatchHandler().handle_patch_application(feature, structed_plan):
|
|
74
|
-
return {
|
|
75
|
-
"success": True,
|
|
76
|
-
"stdout": "Code modification successful",
|
|
77
|
-
"stderr": "",
|
|
78
|
-
}
|
|
79
|
-
else:
|
|
80
|
-
return {
|
|
81
|
-
"success": False,
|
|
82
|
-
"stdout": "",
|
|
83
|
-
"stderr": "Code modification failed, please modify the requirement and try again",
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
except Exception as e:
|
|
87
|
-
return {
|
|
88
|
-
"success": False,
|
|
89
|
-
"stdout": "",
|
|
90
|
-
"stderr": f"Execution failed: {str(e)}, please modify the requirement and try again",
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
def main():
|
|
94
|
-
"""Command line entry"""
|
|
95
|
-
import argparse
|
|
96
|
-
|
|
97
|
-
init_env()
|
|
98
|
-
|
|
99
|
-
parser = argparse.ArgumentParser(description='Code modification tool')
|
|
100
|
-
parser.add_argument('-d', '--dir', help='Project root directory', default=os.getcwd())
|
|
101
|
-
parser.add_argument('-l', '--language', help='Programming language', default="python")
|
|
102
|
-
args = parser.parse_args()
|
|
103
|
-
|
|
104
|
-
tool = JarvisCoder(args.dir, args.language)
|
|
105
|
-
|
|
106
|
-
# Loop through requirements
|
|
107
|
-
while True:
|
|
108
|
-
try:
|
|
109
|
-
# Get requirements, pass in project root directory
|
|
110
|
-
feature = get_multiline_input("Please enter the development requirements (input empty line to exit):", tool.root_dir)
|
|
111
|
-
|
|
112
|
-
if not feature or feature == "__interrupt__":
|
|
113
|
-
break
|
|
114
|
-
|
|
115
|
-
# Execute modification
|
|
116
|
-
result = tool.execute(feature)
|
|
117
|
-
|
|
118
|
-
# Display results
|
|
119
|
-
if result["success"]:
|
|
120
|
-
PrettyOutput.print(result["stdout"], OutputType.SUCCESS)
|
|
121
|
-
else:
|
|
122
|
-
if result.get("stderr"):
|
|
123
|
-
PrettyOutput.print(result["stderr"], OutputType.WARNING)
|
|
124
|
-
PrettyOutput.print("\nYou can modify the requirements and try again", OutputType.INFO)
|
|
125
|
-
|
|
126
|
-
except KeyboardInterrupt:
|
|
127
|
-
print("\nUser interrupted execution")
|
|
128
|
-
break
|
|
129
|
-
except Exception as e:
|
|
130
|
-
PrettyOutput.print(f"Execution failed: {str(e)}", OutputType.ERROR)
|
|
131
|
-
PrettyOutput.print("\nYou can modify the requirements and try again", OutputType.INFO)
|
|
132
|
-
continue
|
|
133
|
-
|
|
134
|
-
return 0
|
|
135
|
-
|
|
136
|
-
if __name__ == "__main__":
|
|
137
|
-
exit(main())
|
|
138
|
-
|
|
139
|
-
class FilePathCompleter(Completer):
|
|
140
|
-
"""File path auto-completer"""
|
|
141
|
-
|
|
142
|
-
def __init__(self, root_dir: str):
|
|
143
|
-
self.root_dir = root_dir
|
|
144
|
-
self._file_list = None
|
|
145
|
-
|
|
146
|
-
def _get_files(self) -> List[str]:
|
|
147
|
-
"""Get the list of files managed by git"""
|
|
148
|
-
if self._file_list is None:
|
|
149
|
-
try:
|
|
150
|
-
# Switch to project root directory
|
|
151
|
-
old_cwd = os.getcwd()
|
|
152
|
-
os.chdir(self.root_dir)
|
|
153
|
-
|
|
154
|
-
# Get the list of files managed by git
|
|
155
|
-
self._file_list = os.popen("git ls-files").read().splitlines()
|
|
156
|
-
|
|
157
|
-
# Restore working directory
|
|
158
|
-
os.chdir(old_cwd)
|
|
159
|
-
except Exception as e:
|
|
160
|
-
PrettyOutput.print(f"Failed to get file list: {str(e)}", OutputType.WARNING)
|
|
161
|
-
self._file_list = []
|
|
162
|
-
return self._file_list
|
|
163
|
-
|
|
164
|
-
def get_completions(self, document, complete_event):
|
|
165
|
-
"""Get completion suggestions"""
|
|
166
|
-
text_before_cursor = document.text_before_cursor
|
|
167
|
-
|
|
168
|
-
# Check if @ was just entered
|
|
169
|
-
if text_before_cursor.endswith('@'):
|
|
170
|
-
# Display all files
|
|
171
|
-
for path in self._get_files():
|
|
172
|
-
yield Completion(path, start_position=0)
|
|
173
|
-
return
|
|
174
|
-
|
|
175
|
-
# Check if there was an @ before, and get the search word after @
|
|
176
|
-
at_pos = text_before_cursor.rfind('@')
|
|
177
|
-
if at_pos == -1:
|
|
178
|
-
return
|
|
179
|
-
|
|
180
|
-
search = text_before_cursor[at_pos + 1:].lower().strip()
|
|
181
|
-
|
|
182
|
-
# Provide matching file suggestions
|
|
183
|
-
for path in self._get_files():
|
|
184
|
-
path_lower = path.lower()
|
|
185
|
-
if (search in path_lower or # Directly included
|
|
186
|
-
search in os.path.basename(path_lower) or # File name included
|
|
187
|
-
any(fnmatch.fnmatch(path_lower, f'*{s}*') for s in search.split())): # Wildcard matching
|
|
188
|
-
# Calculate the correct start_position
|
|
189
|
-
yield Completion(path, start_position=-(len(search)))
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
def get_multiline_input(prompt_text: str, root_dir: Optional[str] = ".") -> str:
|
|
193
|
-
"""Get multi-line input, support file path auto-completion function
|
|
194
|
-
|
|
195
|
-
Args:
|
|
196
|
-
prompt_text: Prompt text
|
|
197
|
-
root_dir: Project root directory, for file completion
|
|
198
|
-
|
|
199
|
-
Returns:
|
|
200
|
-
str: User input text
|
|
201
|
-
"""
|
|
202
|
-
# Create file completion
|
|
203
|
-
file_completer = FilePathCompleter(root_dir or os.getcwd())
|
|
204
|
-
|
|
205
|
-
# Create prompt style
|
|
206
|
-
style = Style.from_dict({
|
|
207
|
-
'prompt': 'ansicyan bold',
|
|
208
|
-
'input': 'ansiwhite',
|
|
209
|
-
})
|
|
210
|
-
|
|
211
|
-
# Create session
|
|
212
|
-
session = PromptSession(
|
|
213
|
-
completer=file_completer,
|
|
214
|
-
style=style,
|
|
215
|
-
multiline=False,
|
|
216
|
-
enable_history_search=True,
|
|
217
|
-
complete_while_typing=True
|
|
218
|
-
)
|
|
219
|
-
|
|
220
|
-
# Display initial prompt text
|
|
221
|
-
print(f"\n{prompt_text}")
|
|
222
|
-
|
|
223
|
-
# Create prompt
|
|
224
|
-
prompt = FormattedText([
|
|
225
|
-
('class:prompt', ">>> ")
|
|
226
|
-
])
|
|
227
|
-
|
|
228
|
-
# Get input
|
|
229
|
-
lines = []
|
|
230
|
-
try:
|
|
231
|
-
while True:
|
|
232
|
-
line = session.prompt(prompt).strip()
|
|
233
|
-
if not line: # Empty line means input end
|
|
234
|
-
break
|
|
235
|
-
lines.append(line)
|
|
236
|
-
except KeyboardInterrupt:
|
|
237
|
-
return "__interrupt__"
|
|
238
|
-
except EOFError:
|
|
239
|
-
pass
|
|
240
|
-
|
|
241
|
-
return "\n".join(lines)
|
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
import re
|
|
2
|
-
from typing import Dict, List, Tuple
|
|
3
|
-
from jarvis.models.registry import PlatformRegistry
|
|
4
|
-
from jarvis.utils import PrettyOutput, OutputType, get_multiline_input, get_single_line_input, is_long_context
|
|
5
|
-
|
|
6
|
-
class PlanGenerator:
|
|
7
|
-
"""Modification plan generator"""
|
|
8
|
-
|
|
9
|
-
def _build_prompt(self, feature: str, related_files: List[Dict], additional_info: str) -> str:
|
|
10
|
-
"""Build prompt
|
|
11
|
-
|
|
12
|
-
Args:
|
|
13
|
-
feature: Feature description
|
|
14
|
-
related_files: Related files list
|
|
15
|
-
additional_info: User supplement information
|
|
16
|
-
|
|
17
|
-
Returns:
|
|
18
|
-
str: Complete prompt
|
|
19
|
-
"""
|
|
20
|
-
prompt = "You are a code modification expert who can generate modification plans based on requirements and relevant code snippets. I need your help to analyze how to implement the following feature:\n\n"
|
|
21
|
-
prompt += f"{feature}\n\n"
|
|
22
|
-
|
|
23
|
-
prompt += "Here are the relevant code file snippets:\n\n"
|
|
24
|
-
|
|
25
|
-
for file in related_files:
|
|
26
|
-
prompt += f"File: {file['file_path']}\n"
|
|
27
|
-
for part in file["parts"]:
|
|
28
|
-
prompt += f"<PART>\n{part}\n</PART>\n"
|
|
29
|
-
|
|
30
|
-
prompt += "\nPlease provide detailed modifications needed to implement this feature. Include:\n"
|
|
31
|
-
prompt += "1. Which files need to be modified\n"
|
|
32
|
-
prompt += "2. How to modify each file, no explanation needed\n"
|
|
33
|
-
prompt += "3. Don't assume other files or code exist, only generate modification plans based on provided file contents and description\n"
|
|
34
|
-
prompt += "4. Don't implement features outside the requirement\n"
|
|
35
|
-
prompt += "5. Output only one modification plan per file (can be multiple lines)\n"
|
|
36
|
-
prompt += "6. Output format as follows:\n"
|
|
37
|
-
prompt += "<PLAN>\n"
|
|
38
|
-
prompt += "> path/to/file1\n"
|
|
39
|
-
prompt += "modification plan\n"
|
|
40
|
-
prompt += "</PLAN>\n"
|
|
41
|
-
prompt += "<PLAN>\n"
|
|
42
|
-
prompt += "> path/to/file2\n"
|
|
43
|
-
prompt += "modification plan\n"
|
|
44
|
-
prompt += "</PLAN>\n"
|
|
45
|
-
if additional_info:
|
|
46
|
-
prompt += f"# Additional information:\n{additional_info}\n"
|
|
47
|
-
|
|
48
|
-
return prompt
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
@staticmethod
|
|
52
|
-
def get_key_code(files: List[str], feature: str)->List[Dict[str, List[str]]]:
|
|
53
|
-
"""Extract relevant key code snippets from files"""
|
|
54
|
-
ret = []
|
|
55
|
-
for file in files:
|
|
56
|
-
PrettyOutput.print(f"Analyzing file: {file}", OutputType.INFO)
|
|
57
|
-
model = PlatformRegistry.get_global_platform_registry().get_codegen_platform()
|
|
58
|
-
model.set_suppress_output(True)
|
|
59
|
-
file_path = file
|
|
60
|
-
content = open(file_path, "r", encoding="utf-8").read()
|
|
61
|
-
|
|
62
|
-
try:
|
|
63
|
-
prompt = f"""You are a code analysis expert who can extract relevant snippets from code.
|
|
64
|
-
Please return in the following format:
|
|
65
|
-
<PART>
|
|
66
|
-
content
|
|
67
|
-
</PART>
|
|
68
|
-
|
|
69
|
-
Multiple snippets can be returned. If the file content is not relevant to the requirement, return empty.
|
|
70
|
-
|
|
71
|
-
Requirement: {feature}
|
|
72
|
-
File path: {file_path}
|
|
73
|
-
Code content:
|
|
74
|
-
{content}
|
|
75
|
-
"""
|
|
76
|
-
|
|
77
|
-
# 调用大模型进行分析
|
|
78
|
-
response = model.chat_until_success(prompt)
|
|
79
|
-
|
|
80
|
-
parts = re.findall(r'<PART>\n(.*?)\n</PART>', response, re.DOTALL)
|
|
81
|
-
ret.append({"file_path": file, "parts": parts})
|
|
82
|
-
except Exception as e:
|
|
83
|
-
PrettyOutput.print(f"Failed to analyze file: {str(e)}", OutputType.ERROR)
|
|
84
|
-
return ret
|
|
85
|
-
|
|
86
|
-
def generate_plan(self, feature: str, related_files: List[str]) -> Dict[str,str]:
|
|
87
|
-
"""Generate modification plan
|
|
88
|
-
|
|
89
|
-
Args:
|
|
90
|
-
feature: Feature description
|
|
91
|
-
related_files: Related files list
|
|
92
|
-
|
|
93
|
-
Returns:
|
|
94
|
-
Tuple[str, Dict[str,str]]: Modification plan, return None if user cancels
|
|
95
|
-
"""
|
|
96
|
-
additional_info = ""
|
|
97
|
-
file_info = []
|
|
98
|
-
if is_long_context(related_files):
|
|
99
|
-
file_info = PlanGenerator.get_key_code(related_files, feature)
|
|
100
|
-
else:
|
|
101
|
-
for file in related_files:
|
|
102
|
-
file_info.append({"file_path": file, "parts": [open(file, "r", encoding="utf-8").read()]})
|
|
103
|
-
|
|
104
|
-
while True:
|
|
105
|
-
prompt = self._build_prompt(feature, file_info, additional_info)
|
|
106
|
-
# Build prompt
|
|
107
|
-
PrettyOutput.print("Start generating modification plan...", OutputType.PROGRESS)
|
|
108
|
-
|
|
109
|
-
# Get modification plan
|
|
110
|
-
raw_plan = PlatformRegistry.get_global_platform_registry().get_thinking_platform().chat_until_success(prompt)
|
|
111
|
-
structed_plan = self._extract_code(raw_plan)
|
|
112
|
-
if not structed_plan:
|
|
113
|
-
PrettyOutput.print("Modification plan generation failed, please try again", OutputType.ERROR)
|
|
114
|
-
tmp = get_multiline_input("Please enter your additional information or suggestions (press Enter to cancel):")
|
|
115
|
-
if tmp == "__interrupt__" or prompt == "":
|
|
116
|
-
return {}
|
|
117
|
-
additional_info += tmp + "\n"
|
|
118
|
-
continue
|
|
119
|
-
user_input = get_single_line_input("Do you agree with this modification plan? (y/n) [y]").strip().lower() or 'y'
|
|
120
|
-
if user_input == 'y' or user_input == '':
|
|
121
|
-
return structed_plan
|
|
122
|
-
elif user_input == 'n':
|
|
123
|
-
# Get user feedback
|
|
124
|
-
tmp = get_multiline_input("Please enter your additional information or suggestions (press Enter to cancel):")
|
|
125
|
-
if prompt == "__interrupt__" or prompt == "":
|
|
126
|
-
return {}
|
|
127
|
-
additional_info += tmp + "\n"
|
|
128
|
-
continue
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
def _extract_code(self, response: str) -> Dict[str, str]:
|
|
132
|
-
"""Extract code from response
|
|
133
|
-
|
|
134
|
-
Args:
|
|
135
|
-
response: Model response content
|
|
136
|
-
|
|
137
|
-
Returns:
|
|
138
|
-
Dict[str, List[str]]: Code dictionary, key is file path, value is code snippet list
|
|
139
|
-
"""
|
|
140
|
-
code_dict = {}
|
|
141
|
-
for match in re.finditer(r'<PLAN>\n> (.+?)\n(.*?)\n</PLAN>', response, re.DOTALL):
|
|
142
|
-
file_path = match.group(1)
|
|
143
|
-
code_part = match.group(2)
|
|
144
|
-
code_dict[file_path] = code_part
|
|
145
|
-
return code_dict
|
jarvis/jarvis_rag/__init__.py
DELETED
|
File without changes
|