mirror of
https://github.com/Aider-AI/aider.git
synced 2025-06-06 04:35:00 +00:00
Merge branch 'main' into mixpanel
This commit is contained in:
commit
068fb38a5d
181 changed files with 141428 additions and 1961 deletions
|
@ -1,10 +1,15 @@
|
|||
from .architect_coder import ArchitectCoder
|
||||
from .ask_coder import AskCoder
|
||||
from .base_coder import Coder
|
||||
from .editblock_coder import EditBlockCoder
|
||||
from .editblock_fenced_coder import EditBlockFencedCoder
|
||||
from .editor_editblock_coder import EditorEditBlockCoder
|
||||
from .editor_whole_coder import EditorWholeFileCoder
|
||||
from .help_coder import HelpCoder
|
||||
from .udiff_coder import UnifiedDiffCoder
|
||||
from .wholefile_coder import WholeFileCoder
|
||||
from .ask_coder import AskCoder
|
||||
|
||||
# from .single_wholefile_func_coder import SingleWholeFileFunctionCoder
|
||||
|
||||
__all__ = [
|
||||
HelpCoder,
|
||||
|
@ -14,4 +19,8 @@ __all__ = [
|
|||
EditBlockFencedCoder,
|
||||
WholeFileCoder,
|
||||
UnifiedDiffCoder,
|
||||
# SingleWholeFileFunctionCoder,
|
||||
ArchitectCoder,
|
||||
EditorEditBlockCoder,
|
||||
EditorWholeFileCoder,
|
||||
]
|
||||
|
|
44
aider/coders/architect_coder.py
Normal file
44
aider/coders/architect_coder.py
Normal file
|
@ -0,0 +1,44 @@
|
|||
from .architect_prompts import ArchitectPrompts
|
||||
from .ask_coder import AskCoder
|
||||
from .base_coder import Coder
|
||||
|
||||
|
||||
class ArchitectCoder(AskCoder):
|
||||
edit_format = "architect"
|
||||
gpt_prompts = ArchitectPrompts()
|
||||
|
||||
def reply_completed(self):
|
||||
content = self.partial_response_content
|
||||
|
||||
if not self.io.confirm_ask("Edit the files?"):
|
||||
return
|
||||
|
||||
kwargs = dict()
|
||||
|
||||
# Use the editor_model from the main_model if it exists, otherwise use the main_model itself
|
||||
editor_model = self.main_model.editor_model or self.main_model
|
||||
|
||||
kwargs["main_model"] = editor_model
|
||||
kwargs["edit_format"] = self.main_model.editor_edit_format
|
||||
kwargs["suggest_shell_commands"] = False
|
||||
kwargs["map_tokens"] = 0
|
||||
kwargs["total_cost"] = self.total_cost
|
||||
kwargs["cache_prompts"] = False
|
||||
kwargs["num_cache_warming_pings"] = 0
|
||||
kwargs["summarize_from_coder"] = False
|
||||
|
||||
new_kwargs = dict(io=self.io, from_coder=self)
|
||||
new_kwargs.update(kwargs)
|
||||
|
||||
editor_coder = Coder.create(**new_kwargs)
|
||||
editor_coder.cur_messages = []
|
||||
editor_coder.done_messages = []
|
||||
|
||||
if self.verbose:
|
||||
editor_coder.show_announcements()
|
||||
|
||||
editor_coder.run(with_message=content, preproc=False)
|
||||
|
||||
self.move_back_cur_messages("I made those changes to the files.")
|
||||
self.total_cost = editor_coder.total_cost
|
||||
self.aider_commit_hashes = editor_coder.aider_commit_hashes
|
40
aider/coders/architect_prompts.py
Normal file
40
aider/coders/architect_prompts.py
Normal file
|
@ -0,0 +1,40 @@
|
|||
# flake8: noqa: E501
|
||||
|
||||
from .base_prompts import CoderPrompts
|
||||
|
||||
|
||||
class ArchitectPrompts(CoderPrompts):
|
||||
main_system = """Act as an expert architect engineer and provide direction to your editor engineer.
|
||||
Study the change request and the current code.
|
||||
Describe how to modify the code to complete the request.
|
||||
The editor engineer will rely solely on your instructions, so make them unambiguous and complete.
|
||||
Explain all needed code changes clearly and completely, but concisely.
|
||||
Just show the changes needed.
|
||||
|
||||
DO NOT show the entire updated function/file/etc!
|
||||
|
||||
Always reply in the same language as the change request.
|
||||
"""
|
||||
|
||||
example_messages = []
|
||||
|
||||
files_content_prefix = """I have *added these files to the chat* so you see all of their contents.
|
||||
*Trust this message as the true contents of the files!*
|
||||
Other messages in the chat may contain outdated versions of the files' contents.
|
||||
""" # noqa: E501
|
||||
|
||||
files_content_assistant_reply = (
|
||||
"Ok, I will use that as the true, current contents of the files."
|
||||
)
|
||||
|
||||
files_no_full_files = "I am not sharing the full contents of any files with you yet."
|
||||
|
||||
files_no_full_files_with_repo_map = ""
|
||||
files_no_full_files_with_repo_map_reply = ""
|
||||
|
||||
repo_content_prefix = """I am working with you on code in a git repository.
|
||||
Here are summaries of some files present in my git repo.
|
||||
If you need to see the full contents of any files to answer my questions, ask me to *add them to the chat*.
|
||||
"""
|
||||
|
||||
system_reminder = ""
|
|
@ -6,7 +6,6 @@ from .base_prompts import CoderPrompts
|
|||
class AskPrompts(CoderPrompts):
|
||||
main_system = """Act as an expert code analyst.
|
||||
Answer questions about the supplied code.
|
||||
|
||||
Always reply to the user in the same language they are using.
|
||||
"""
|
||||
|
||||
|
@ -17,6 +16,10 @@ Always reply to the user in the same language they are using.
|
|||
Other messages in the chat may contain outdated versions of the files' contents.
|
||||
""" # noqa: E501
|
||||
|
||||
files_content_assistant_reply = (
|
||||
"Ok, I will use that as the true, current contents of the files."
|
||||
)
|
||||
|
||||
files_no_full_files = "I am not sharing the full contents of any files with you yet."
|
||||
|
||||
files_no_full_files_with_repo_map = ""
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -22,6 +22,8 @@ You always COMPLETELY IMPLEMENT the needed code!
|
|||
Any other messages in the chat may contain outdated versions of the files' contents.
|
||||
""" # noqa: E501
|
||||
|
||||
files_content_assistant_reply = "Ok, any changes I propose will be to those files."
|
||||
|
||||
files_no_full_files = "I am not sharing any files that you can edit yet."
|
||||
|
||||
files_no_full_files_with_repo_map = """Don't try and edit any existing code without asking me to add the files to the chat!
|
||||
|
@ -43,3 +45,8 @@ If you need to edit any of these files, ask me to *add them to the chat* first.
|
|||
read_only_files_prefix = """Here are some READ ONLY files, provided for your reference.
|
||||
Do not edit these files!
|
||||
"""
|
||||
|
||||
shell_cmd_prompt = ""
|
||||
shell_cmd_reminder = ""
|
||||
no_shell_cmd_prompt = ""
|
||||
no_shell_cmd_reminder = ""
|
||||
|
|
64
aider/coders/chat_chunks.py
Normal file
64
aider/coders/chat_chunks.py
Normal file
|
@ -0,0 +1,64 @@
|
|||
from dataclasses import dataclass, field
|
||||
from typing import List
|
||||
|
||||
|
||||
@dataclass
|
||||
class ChatChunks:
|
||||
system: List = field(default_factory=list)
|
||||
examples: List = field(default_factory=list)
|
||||
done: List = field(default_factory=list)
|
||||
repo: List = field(default_factory=list)
|
||||
readonly_files: List = field(default_factory=list)
|
||||
chat_files: List = field(default_factory=list)
|
||||
cur: List = field(default_factory=list)
|
||||
reminder: List = field(default_factory=list)
|
||||
|
||||
def all_messages(self):
|
||||
return (
|
||||
self.system
|
||||
+ self.examples
|
||||
+ self.readonly_files
|
||||
+ self.repo
|
||||
+ self.done
|
||||
+ self.chat_files
|
||||
+ self.cur
|
||||
+ self.reminder
|
||||
)
|
||||
|
||||
def add_cache_control_headers(self):
|
||||
if self.examples:
|
||||
self.add_cache_control(self.examples)
|
||||
else:
|
||||
self.add_cache_control(self.system)
|
||||
|
||||
if self.repo:
|
||||
# this will mark both the readonly_files and repomap chunk as cacheable
|
||||
self.add_cache_control(self.repo)
|
||||
else:
|
||||
# otherwise, just cache readonly_files if there are any
|
||||
self.add_cache_control(self.readonly_files)
|
||||
|
||||
self.add_cache_control(self.chat_files)
|
||||
|
||||
def add_cache_control(self, messages):
|
||||
if not messages:
|
||||
return
|
||||
|
||||
content = messages[-1]["content"]
|
||||
if type(content) is str:
|
||||
content = dict(
|
||||
type="text",
|
||||
text=content,
|
||||
)
|
||||
content["cache_control"] = {"type": "ephemeral"}
|
||||
|
||||
messages[-1]["content"] = [content]
|
||||
|
||||
def cacheable_messages(self):
|
||||
messages = self.all_messages()
|
||||
for i, message in enumerate(reversed(messages)):
|
||||
if isinstance(message.get("content"), list) and message["content"][0].get(
|
||||
"cache_control"
|
||||
):
|
||||
return messages[: len(messages) - i]
|
||||
return messages
|
|
@ -14,6 +14,7 @@ from .editblock_prompts import EditBlockPrompts
|
|||
|
||||
class EditBlockCoder(Coder):
|
||||
"""A coder that uses search/replace blocks for code modifications."""
|
||||
|
||||
edit_format = "diff"
|
||||
gpt_prompts = EditBlockPrompts()
|
||||
|
||||
|
@ -21,13 +22,27 @@ class EditBlockCoder(Coder):
|
|||
content = self.partial_response_content
|
||||
|
||||
# might raise ValueError for malformed ORIG/UPD blocks
|
||||
edits = list(find_original_update_blocks(content, self.fence))
|
||||
edits = list(
|
||||
find_original_update_blocks(
|
||||
content,
|
||||
self.fence,
|
||||
self.get_inchat_relative_files(),
|
||||
)
|
||||
)
|
||||
|
||||
self.shell_commands += [edit[1] for edit in edits if edit[0] is None]
|
||||
edits = [edit for edit in edits if edit[0] is not None]
|
||||
|
||||
return edits
|
||||
|
||||
def apply_edits(self, edits):
|
||||
def apply_edits_dry_run(self, edits):
|
||||
return self.apply_edits(edits, dry_run=True)
|
||||
|
||||
def apply_edits(self, edits, dry_run=False):
|
||||
failed = []
|
||||
passed = []
|
||||
updated_edits = []
|
||||
|
||||
for edit in edits:
|
||||
path, original, updated = edit
|
||||
full_path = self.abs_root_path(path)
|
||||
|
@ -39,14 +54,21 @@ class EditBlockCoder(Coder):
|
|||
content = self.io.read_text(full_path)
|
||||
new_content = do_replace(full_path, content, original, updated, self.fence)
|
||||
if new_content:
|
||||
path = self.get_rel_fname(full_path)
|
||||
break
|
||||
|
||||
updated_edits.append((path, original, updated))
|
||||
|
||||
if new_content:
|
||||
self.io.write_text(full_path, new_content)
|
||||
if not dry_run:
|
||||
self.io.write_text(full_path, new_content)
|
||||
passed.append(edit)
|
||||
else:
|
||||
failed.append(edit)
|
||||
|
||||
if dry_run:
|
||||
return updated_edits
|
||||
|
||||
if not failed:
|
||||
return
|
||||
|
||||
|
@ -354,9 +376,13 @@ def do_replace(fname, content, before_text, after_text, fence=None):
|
|||
return new_content
|
||||
|
||||
|
||||
HEAD = "<<<<<<< SEARCH"
|
||||
DIVIDER = "======="
|
||||
UPDATED = ">>>>>>> REPLACE"
|
||||
HEAD = r"^<{5,9} SEARCH\s*$"
|
||||
DIVIDER = r"^={5,9}\s*$"
|
||||
UPDATED = r"^>{5,9} REPLACE\s*$"
|
||||
|
||||
HEAD_ERR = "<<<<<<< SEARCH"
|
||||
DIVIDER_ERR = "======="
|
||||
UPDATED_ERR = ">>>>>>> REPLACE"
|
||||
|
||||
separators = "|".join([HEAD, DIVIDER, UPDATED])
|
||||
|
||||
|
@ -384,77 +410,106 @@ def strip_filename(filename, fence):
|
|||
filename = filename.strip()
|
||||
filename = filename.strip("`")
|
||||
filename = filename.strip("*")
|
||||
filename = filename.replace("\\_", "_")
|
||||
|
||||
# https://github.com/Aider-AI/aider/issues/1158
|
||||
# filename = filename.replace("\\_", "_")
|
||||
|
||||
return filename
|
||||
|
||||
|
||||
def find_original_update_blocks(content, fence=DEFAULT_FENCE):
|
||||
# make sure we end with a newline, otherwise the regex will miss <<UPD on the last line
|
||||
if not content.endswith("\n"):
|
||||
content = content + "\n"
|
||||
|
||||
pieces = re.split(split_re, content)
|
||||
|
||||
pieces.reverse()
|
||||
processed = []
|
||||
|
||||
# Keep using the same filename in cases where GPT produces an edit block
|
||||
# without a filename.
|
||||
def find_original_update_blocks(content, fence=DEFAULT_FENCE, valid_fnames=None):
|
||||
lines = content.splitlines(keepends=True)
|
||||
i = 0
|
||||
current_filename = None
|
||||
try:
|
||||
while pieces:
|
||||
cur = pieces.pop()
|
||||
|
||||
if cur in (DIVIDER, UPDATED):
|
||||
processed.append(cur)
|
||||
raise ValueError(f"Unexpected {cur}")
|
||||
head_pattern = re.compile(HEAD)
|
||||
divider_pattern = re.compile(DIVIDER)
|
||||
updated_pattern = re.compile(UPDATED)
|
||||
|
||||
if cur.strip() != HEAD:
|
||||
processed.append(cur)
|
||||
continue
|
||||
while i < len(lines):
|
||||
line = lines[i]
|
||||
|
||||
processed.append(cur) # original_marker
|
||||
# Check for shell code blocks
|
||||
shell_starts = [
|
||||
"```bash",
|
||||
"```sh",
|
||||
"```shell",
|
||||
"```cmd",
|
||||
"```batch",
|
||||
"```powershell",
|
||||
"```ps1",
|
||||
"```zsh",
|
||||
"```fish",
|
||||
"```ksh",
|
||||
"```csh",
|
||||
"```tcsh",
|
||||
]
|
||||
next_is_editblock = i + 1 < len(lines) and head_pattern.match(lines[i + 1].strip())
|
||||
|
||||
filename = find_filename(processed[-2].splitlines(), fence)
|
||||
if not filename:
|
||||
if current_filename:
|
||||
filename = current_filename
|
||||
if any(line.strip().startswith(start) for start in shell_starts) and not next_is_editblock:
|
||||
shell_content = []
|
||||
i += 1
|
||||
while i < len(lines) and not lines[i].strip().startswith("```"):
|
||||
shell_content.append(lines[i])
|
||||
i += 1
|
||||
if i < len(lines) and lines[i].strip().startswith("```"):
|
||||
i += 1 # Skip the closing ```
|
||||
|
||||
yield None, "".join(shell_content)
|
||||
continue
|
||||
|
||||
# Check for SEARCH/REPLACE blocks
|
||||
if head_pattern.match(line.strip()):
|
||||
try:
|
||||
# if next line after HEAD exists and is DIVIDER, it's a new file
|
||||
if i + 1 < len(lines) and divider_pattern.match(lines[i + 1].strip()):
|
||||
filename = find_filename(lines[max(0, i - 3) : i], fence, None)
|
||||
else:
|
||||
raise ValueError(missing_filename_err.format(fence=fence))
|
||||
filename = find_filename(lines[max(0, i - 3) : i], fence, valid_fnames)
|
||||
|
||||
current_filename = filename
|
||||
if not filename:
|
||||
if current_filename:
|
||||
filename = current_filename
|
||||
else:
|
||||
raise ValueError(missing_filename_err.format(fence=fence))
|
||||
|
||||
original_text = pieces.pop()
|
||||
processed.append(original_text)
|
||||
current_filename = filename
|
||||
|
||||
divider_marker = pieces.pop()
|
||||
processed.append(divider_marker)
|
||||
if divider_marker.strip() != DIVIDER:
|
||||
raise ValueError(f"Expected `{DIVIDER}` not {divider_marker.strip()}")
|
||||
original_text = []
|
||||
i += 1
|
||||
while i < len(lines) and not divider_pattern.match(lines[i].strip()):
|
||||
original_text.append(lines[i])
|
||||
i += 1
|
||||
|
||||
updated_text = pieces.pop()
|
||||
processed.append(updated_text)
|
||||
if i >= len(lines) or not divider_pattern.match(lines[i].strip()):
|
||||
raise ValueError(f"Expected `{DIVIDER_ERR}`")
|
||||
|
||||
updated_marker = pieces.pop()
|
||||
processed.append(updated_marker)
|
||||
if updated_marker.strip() != UPDATED:
|
||||
raise ValueError(f"Expected `{UPDATED}` not `{updated_marker.strip()}")
|
||||
updated_text = []
|
||||
i += 1
|
||||
while i < len(lines) and not (
|
||||
updated_pattern.match(lines[i].strip())
|
||||
or divider_pattern.match(lines[i].strip())
|
||||
):
|
||||
updated_text.append(lines[i])
|
||||
i += 1
|
||||
|
||||
yield filename, original_text, updated_text
|
||||
except ValueError as e:
|
||||
processed = "".join(processed)
|
||||
err = e.args[0]
|
||||
raise ValueError(f"{processed}\n^^^ {err}")
|
||||
except IndexError:
|
||||
processed = "".join(processed)
|
||||
raise ValueError(f"{processed}\n^^^ Incomplete SEARCH/REPLACE block.")
|
||||
except Exception:
|
||||
processed = "".join(processed)
|
||||
raise ValueError(f"{processed}\n^^^ Error parsing SEARCH/REPLACE block.")
|
||||
if i >= len(lines) or not (
|
||||
updated_pattern.match(lines[i].strip())
|
||||
or divider_pattern.match(lines[i].strip())
|
||||
):
|
||||
raise ValueError(f"Expected `{UPDATED_ERR}` or `{DIVIDER_ERR}`")
|
||||
|
||||
yield filename, "".join(original_text), "".join(updated_text)
|
||||
|
||||
except ValueError as e:
|
||||
processed = "".join(lines[: i + 1])
|
||||
err = e.args[0]
|
||||
raise ValueError(f"{processed}\n^^^ {err}")
|
||||
|
||||
i += 1
|
||||
|
||||
|
||||
def find_filename(lines, fence):
|
||||
def find_filename(lines, fence, valid_fnames):
|
||||
"""
|
||||
Deepseek Coder v2 has been doing this:
|
||||
|
||||
|
@ -468,19 +523,54 @@ def find_filename(lines, fence):
|
|||
|
||||
This is a more flexible search back for filenames.
|
||||
"""
|
||||
|
||||
if valid_fnames is None:
|
||||
valid_fnames = []
|
||||
|
||||
# Go back through the 3 preceding lines
|
||||
lines.reverse()
|
||||
lines = lines[:3]
|
||||
|
||||
filenames = []
|
||||
for line in lines:
|
||||
# If we find a filename, done
|
||||
filename = strip_filename(line, fence)
|
||||
if filename:
|
||||
return filename
|
||||
filenames.append(filename)
|
||||
|
||||
# Only continue as long as we keep seeing fences
|
||||
if not line.startswith(fence[0]):
|
||||
return
|
||||
break
|
||||
|
||||
if not filenames:
|
||||
return
|
||||
|
||||
# pick the *best* filename found
|
||||
|
||||
# Check for exact match first
|
||||
for fname in filenames:
|
||||
if fname in valid_fnames:
|
||||
return fname
|
||||
|
||||
# Check for partial match (basename match)
|
||||
for fname in filenames:
|
||||
for vfn in valid_fnames:
|
||||
if fname == Path(vfn).name:
|
||||
return vfn
|
||||
|
||||
# Perform fuzzy matching with valid_fnames
|
||||
for fname in filenames:
|
||||
close_matches = difflib.get_close_matches(fname, valid_fnames, n=1, cutoff=0.8)
|
||||
if len(close_matches) == 1:
|
||||
return close_matches[0]
|
||||
|
||||
# If no fuzzy match, look for a file w/extension
|
||||
for fname in filenames:
|
||||
if "." in fname:
|
||||
return fname
|
||||
|
||||
if filenames:
|
||||
return filenames[0]
|
||||
|
||||
|
||||
def find_similar_lines(search_lines, content_lines, threshold=0.6):
|
||||
|
|
|
@ -111,9 +111,9 @@ class EditBlockFunctionCoder(Coder):
|
|||
updated = get_arg(edit, "updated_lines")
|
||||
|
||||
# gpt-3.5 returns lists even when instructed to return a string!
|
||||
if self.code_format == "list" or type(original) == list:
|
||||
if self.code_format == "list" or type(original) is list:
|
||||
original = "\n".join(original)
|
||||
if self.code_format == "list" or type(updated) == list:
|
||||
if self.code_format == "list" or type(updated) is list:
|
||||
updated = "\n".join(updated)
|
||||
|
||||
if original and not original.endswith("\n"):
|
||||
|
|
|
@ -14,16 +14,45 @@ If the request is ambiguous, ask questions.
|
|||
Always reply to the user in the same language they are using.
|
||||
|
||||
Once you understand the request you MUST:
|
||||
1. Decide if you need to propose *SEARCH/REPLACE* edits to any files that haven't been added to the chat. You can create new files without asking. But if you need to propose edits to existing files not already added to the chat, you *MUST* tell the user their full path names and ask them to *add the files to the chat*. End your reply and wait for their approval. You can keep asking if you then decide you need to edit more files.
|
||||
2. Think step-by-step and explain the needed changes with a numbered list of short sentences.
|
||||
3. Describe each change with a *SEARCH/REPLACE block* per the examples below. All changes to files must use this *SEARCH/REPLACE block* format. ONLY EVER RETURN CODE IN A *SEARCH/REPLACE BLOCK*!
|
||||
|
||||
All changes to files must use the *SEARCH/REPLACE block* format.
|
||||
1. Decide if you need to propose *SEARCH/REPLACE* edits to any files that haven't been added to the chat. You can create new files without asking!
|
||||
|
||||
Keep this info about the user's system in mind:
|
||||
{platform}
|
||||
But if you need to propose edits to existing files not already added to the chat, you *MUST* tell the user their full path names and ask them to *add the files to the chat*.
|
||||
End your reply and wait for their approval.
|
||||
You can keep asking if you then decide you need to edit more files.
|
||||
|
||||
2. Think step-by-step and explain the needed changes in a few short sentences.
|
||||
|
||||
3. Describe each change with a *SEARCH/REPLACE block* per the examples below.
|
||||
|
||||
All changes to files must use this *SEARCH/REPLACE block* format.
|
||||
ONLY EVER RETURN CODE IN A *SEARCH/REPLACE BLOCK*!
|
||||
{shell_cmd_prompt}
|
||||
"""
|
||||
|
||||
shell_cmd_prompt = """
|
||||
4. *Concisely* suggest any shell commands the user might want to run in ```bash blocks.
|
||||
|
||||
Just suggest shell commands this way, not example code.
|
||||
Only suggest complete shell commands that are ready to execute, without placeholders.
|
||||
Only suggest at most a few shell commands at a time, not more than 1-3.
|
||||
|
||||
Use the appropriate shell based on the user's system info:
|
||||
{platform}
|
||||
Examples of when to suggest shell commands:
|
||||
|
||||
- If you changed a self-contained html file, suggest an OS-appropriate command to open a browser to view it to see the updated content.
|
||||
- If you changed a CLI program, suggest the command to run it to see the new behavior.
|
||||
- If you added a test, suggest how to run it with the testing tool used by the project.
|
||||
- Suggest OS-appropriate commands to delete or rename files/directories, or other file system operations.
|
||||
- If your code changes add new dependencies, suggest the command to install them.
|
||||
- Etc.
|
||||
"""
|
||||
|
||||
no_shell_cmd_prompt = """
|
||||
Keep in mind these details about the user's platform and environment:
|
||||
{platform}
|
||||
"""
|
||||
example_messages = [
|
||||
dict(
|
||||
role="user",
|
||||
|
@ -116,7 +145,7 @@ from hello import hello
|
|||
system_reminder = """# *SEARCH/REPLACE block* Rules:
|
||||
|
||||
Every *SEARCH/REPLACE block* must use this format:
|
||||
1. The file path alone on a line, verbatim. No bold asterisks, no quotes around it, no escaping of characters, etc.
|
||||
1. The *FULL* file path alone on a line, verbatim. No bold asterisks, no quotes around it, no escaping of characters, etc.
|
||||
2. The opening fence and code language, eg: {fence[0]}python
|
||||
3. The start of search block: <<<<<<< SEARCH
|
||||
4. A contiguous chunk of lines to search for in the existing source code
|
||||
|
@ -125,11 +154,14 @@ Every *SEARCH/REPLACE block* must use this format:
|
|||
7. The end of the replace block: >>>>>>> REPLACE
|
||||
8. The closing fence: {fence[1]}
|
||||
|
||||
Use the *FULL* file path, as shown to you by the user.
|
||||
|
||||
Every *SEARCH* section must *EXACTLY MATCH* the existing file content, character for character, including all comments, docstrings, etc.
|
||||
If the file contains code or other data wrapped/escaped in json/xml/quotes or other containers, you need to propose edits to the literal contents of the file, including the container markup.
|
||||
|
||||
*SEARCH/REPLACE* blocks will replace *all* matching occurrences.
|
||||
Include enough lines to make the SEARCH blocks uniquely match the lines to change.
|
||||
*SEARCH/REPLACE* blocks will *only* replace the first match occurrence.
|
||||
Including multiple unique *SEARCH/REPLACE* blocks if needed.
|
||||
Include enough lines in each SEARCH section to uniquely match each set of lines that need to change.
|
||||
|
||||
Keep *SEARCH/REPLACE* blocks concise.
|
||||
Break large *SEARCH/REPLACE* blocks into a series of smaller blocks that each change a small portion of the file.
|
||||
|
@ -140,11 +172,27 @@ Only create *SEARCH/REPLACE* blocks for files that the user has added to the cha
|
|||
|
||||
To move code within a file, use 2 *SEARCH/REPLACE* blocks: 1 to delete it from its current location, 1 to insert it in the new location.
|
||||
|
||||
Pay attention to which filenames the user wants you to edit, especially if they are asking you to create a new file.
|
||||
|
||||
If you want to put code in a new file, use a *SEARCH/REPLACE block* with:
|
||||
- A new file path, including dir name if needed
|
||||
- An empty `SEARCH` section
|
||||
- The new file's contents in the `REPLACE` section
|
||||
|
||||
To rename files which have been added to the chat, use shell commands at the end of your response.
|
||||
|
||||
{lazy_prompt}
|
||||
ONLY EVER RETURN CODE IN A *SEARCH/REPLACE BLOCK*!
|
||||
{shell_cmd_reminder}
|
||||
"""
|
||||
|
||||
shell_cmd_reminder = """
|
||||
Examples of when to suggest shell commands:
|
||||
|
||||
- If you changed a self-contained html file, suggest an OS-appropriate command to open a browser to view it to see the updated content.
|
||||
- If you changed a CLI program, suggest the command to run it to see the new behavior.
|
||||
- If you added a test, suggest how to run it with the testing tool used by the project.
|
||||
- Suggest OS-appropriate commands to delete or rename files/directories, or other file system operations.
|
||||
- If your code changes add new dependencies, suggest the command to install them.
|
||||
- Etc.
|
||||
"""
|
||||
|
|
7
aider/coders/editor_editblock_coder.py
Normal file
7
aider/coders/editor_editblock_coder.py
Normal file
|
@ -0,0 +1,7 @@
|
|||
from .editblock_coder import EditBlockCoder
|
||||
from .editor_editblock_prompts import EditorEditBlockPrompts
|
||||
|
||||
|
||||
class EditorEditBlockCoder(EditBlockCoder):
|
||||
edit_format = "editor-diff"
|
||||
gpt_prompts = EditorEditBlockPrompts()
|
16
aider/coders/editor_editblock_prompts.py
Normal file
16
aider/coders/editor_editblock_prompts.py
Normal file
|
@ -0,0 +1,16 @@
|
|||
# flake8: noqa: E501
|
||||
|
||||
from .editblock_prompts import EditBlockPrompts
|
||||
|
||||
|
||||
class EditorEditBlockPrompts(EditBlockPrompts):
|
||||
main_system = """Act as an expert software developer who edits source code.
|
||||
{lazy_prompt}
|
||||
Describe each change with a *SEARCH/REPLACE block* per the examples below.
|
||||
All changes to files must use this *SEARCH/REPLACE block* format.
|
||||
ONLY EVER RETURN CODE IN A *SEARCH/REPLACE BLOCK*!
|
||||
"""
|
||||
|
||||
shell_cmd_prompt = ""
|
||||
no_shell_cmd_prompt = ""
|
||||
shell_cmd_reminder = ""
|
7
aider/coders/editor_whole_coder.py
Normal file
7
aider/coders/editor_whole_coder.py
Normal file
|
@ -0,0 +1,7 @@
|
|||
from .editor_whole_prompts import EditorWholeFilePrompts
|
||||
from .wholefile_coder import WholeFileCoder
|
||||
|
||||
|
||||
class EditorWholeFileCoder(WholeFileCoder):
|
||||
edit_format = "editor-whole"
|
||||
gpt_prompts = EditorWholeFilePrompts()
|
10
aider/coders/editor_whole_prompts.py
Normal file
10
aider/coders/editor_whole_prompts.py
Normal file
|
@ -0,0 +1,10 @@
|
|||
# flake8: noqa: E501
|
||||
|
||||
from .wholefile_prompts import WholeFilePrompts
|
||||
|
||||
|
||||
class EditorWholeFilePrompts(WholeFilePrompts):
|
||||
main_system = """Act as an expert software developer and make changes to source code.
|
||||
{lazy_prompt}
|
||||
Output a copy of each file that needs changes.
|
||||
"""
|
|
@ -484,7 +484,7 @@ def git_cherry_pick_osr_onto_o(texts):
|
|||
# cherry pick R onto original
|
||||
try:
|
||||
repo.git.cherry_pick(replace_hash, "--minimal")
|
||||
except git.exc.GitCommandError:
|
||||
except (git.exc.ODBError, git.exc.GitError):
|
||||
# merge conflicts!
|
||||
return
|
||||
|
||||
|
@ -522,7 +522,7 @@ def git_cherry_pick_sr_onto_so(texts):
|
|||
# cherry pick replace onto original
|
||||
try:
|
||||
repo.git.cherry_pick(replace_hash, "--minimal")
|
||||
except git.exc.GitCommandError:
|
||||
except (git.exc.ODBError, git.exc.GitError):
|
||||
# merge conflicts!
|
||||
return
|
||||
|
||||
|
|
|
@ -6,13 +6,15 @@ from .single_wholefile_func_prompts import SingleWholeFileFunctionPrompts
|
|||
|
||||
|
||||
class SingleWholeFileFunctionCoder(Coder):
|
||||
edit_format = "func"
|
||||
|
||||
functions = [
|
||||
dict(
|
||||
name="write_file",
|
||||
description="write new content into the file",
|
||||
# strict=True,
|
||||
parameters=dict(
|
||||
type="object",
|
||||
required=["explanation", "content"],
|
||||
properties=dict(
|
||||
explanation=dict(
|
||||
type="string",
|
||||
|
@ -26,12 +28,13 @@ class SingleWholeFileFunctionCoder(Coder):
|
|||
description="Content to write to the file",
|
||||
),
|
||||
),
|
||||
required=["explanation", "content"],
|
||||
additionalProperties=False,
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
raise RuntimeError("Deprecated, needs to be refactored to support get_edits/apply_edits")
|
||||
self.gpt_prompts = SingleWholeFileFunctionPrompts()
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
|
@ -44,33 +47,19 @@ class SingleWholeFileFunctionCoder(Coder):
|
|||
self.cur_messages += [dict(role="assistant", content=self.partial_response_content)]
|
||||
|
||||
def render_incremental_response(self, final=False):
|
||||
res = ""
|
||||
if self.partial_response_content:
|
||||
return self.partial_response_content
|
||||
res += self.partial_response_content
|
||||
|
||||
args = self.parse_partial_args()
|
||||
|
||||
return str(args)
|
||||
|
||||
if not args:
|
||||
return
|
||||
return ""
|
||||
|
||||
explanation = args.get("explanation")
|
||||
files = args.get("files", [])
|
||||
|
||||
res = ""
|
||||
if explanation:
|
||||
res += f"{explanation}\n\n"
|
||||
|
||||
for i, file_upd in enumerate(files):
|
||||
path = file_upd.get("path")
|
||||
if not path:
|
||||
continue
|
||||
content = file_upd.get("content")
|
||||
if not content:
|
||||
continue
|
||||
|
||||
this_final = (i < len(files) - 1) or final
|
||||
res += self.live_diffs(path, content, this_final)
|
||||
for k, v in args.items():
|
||||
res += "\n"
|
||||
res += f"{k}:\n"
|
||||
res += v
|
||||
|
||||
return res
|
||||
|
||||
|
@ -95,18 +84,19 @@ class SingleWholeFileFunctionCoder(Coder):
|
|||
|
||||
return "\n".join(show_diff)
|
||||
|
||||
def _update_files(self):
|
||||
name = self.partial_response_function_call.get("name")
|
||||
if name and name != "write_file":
|
||||
raise ValueError(f'Unknown function_call name="{name}", use name="write_file"')
|
||||
def get_edits(self):
|
||||
chat_files = self.get_inchat_relative_files()
|
||||
assert len(chat_files) == 1, chat_files
|
||||
|
||||
args = self.parse_partial_args()
|
||||
if not args:
|
||||
return
|
||||
return []
|
||||
|
||||
content = args["content"]
|
||||
path = self.get_inchat_relative_files()[0]
|
||||
if self.allowed_to_edit(path, content):
|
||||
return set([path])
|
||||
res = chat_files[0], args["content"]
|
||||
dump(res)
|
||||
return [res]
|
||||
|
||||
return set()
|
||||
def apply_edits(self, edits):
|
||||
for path, content in edits:
|
||||
full_path = self.abs_root_path(path)
|
||||
self.io.write_text(full_path, content)
|
||||
|
|
|
@ -9,17 +9,10 @@ from .wholefile_prompts import WholeFilePrompts
|
|||
|
||||
class WholeFileCoder(Coder):
|
||||
"""A coder that operates on entire files for code modifications."""
|
||||
|
||||
edit_format = "whole"
|
||||
gpt_prompts = WholeFilePrompts()
|
||||
|
||||
def update_cur_messages(self, edited):
|
||||
if edited:
|
||||
self.cur_messages += [
|
||||
dict(role="assistant", content=self.gpt_prompts.redacted_edit_message)
|
||||
]
|
||||
else:
|
||||
self.cur_messages += [dict(role="assistant", content=self.partial_response_content)]
|
||||
|
||||
def render_incremental_response(self, final):
|
||||
try:
|
||||
return self.get_edits(mode="diff")
|
||||
|
@ -65,6 +58,12 @@ class WholeFileCoder(Coder):
|
|||
fname = fname.strip("*") # handle **filename.py**
|
||||
fname = fname.rstrip(":")
|
||||
fname = fname.strip("`")
|
||||
fname = fname.lstrip("#")
|
||||
fname = fname.strip()
|
||||
|
||||
# Issue #1232
|
||||
if len(fname) > 250:
|
||||
fname = ""
|
||||
|
||||
# Did gpt prepend a bogus dir? It especially likes to
|
||||
# include the path/to prefix from the one-shot example in
|
||||
|
@ -130,15 +129,16 @@ class WholeFileCoder(Coder):
|
|||
|
||||
def do_live_diff(self, full_path, new_lines, final):
|
||||
if Path(full_path).exists():
|
||||
orig_lines = self.io.read_text(full_path).splitlines(keepends=True)
|
||||
orig_lines = self.io.read_text(full_path)
|
||||
if orig_lines is not None:
|
||||
orig_lines = orig_lines.splitlines(keepends=True)
|
||||
|
||||
show_diff = diffs.diff_partial_update(
|
||||
orig_lines,
|
||||
new_lines,
|
||||
final=final,
|
||||
).splitlines()
|
||||
output = show_diff
|
||||
else:
|
||||
output = ["```"] + new_lines + ["```"]
|
||||
show_diff = diffs.diff_partial_update(
|
||||
orig_lines,
|
||||
new_lines,
|
||||
final=final,
|
||||
).splitlines()
|
||||
return show_diff
|
||||
|
||||
output = ["```"] + new_lines + ["```"]
|
||||
return output
|
||||
|
|
|
@ -52,7 +52,7 @@ path/to/filename.js
|
|||
{fence[1]}
|
||||
|
||||
Every *file listing* MUST use this format:
|
||||
- First line: the filename with any originally provided path
|
||||
- First line: the filename with any originally provided path; no extra markup, punctuation, comments, etc. **JUST** the filename with path.
|
||||
- Second line: opening {fence[0]}
|
||||
- ... entire content of the file ...
|
||||
- Final line: closing {fence[1]}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue