diff --git a/aider/coders/__init__.py b/aider/coders/__init__.py index 7f85a5622..58f5c3b6c 100644 --- a/aider/coders/__init__.py +++ b/aider/coders/__init__.py @@ -1,7 +1,15 @@ from .base_coder import Coder from .editblock_coder import EditBlockCoder from .editblock_func_coder import EditBlockFunctionCoder +from .single_wholefile_func_coder import SingleWholeFileFunctionCoder from .wholefile_coder import WholeFileCoder from .wholefile_func_coder import WholeFileFunctionCoder -__all__ = [Coder, EditBlockCoder, WholeFileCoder, WholeFileFunctionCoder, EditBlockFunctionCoder] +__all__ = [ + Coder, + EditBlockCoder, + WholeFileCoder, + WholeFileFunctionCoder, + EditBlockFunctionCoder, + SingleWholeFileFunctionCoder, +] diff --git a/aider/coders/base_coder.py b/aider/coders/base_coder.py index e5f87ae2a..a9489e2fd 100755 --- a/aider/coders/base_coder.py +++ b/aider/coders/base_coder.py @@ -59,6 +59,7 @@ class Coder: from . import ( EditBlockCoder, EditBlockFunctionCoder, + SingleWholeFileFunctionCoder, WholeFileCoder, WholeFileFunctionCoder, ) @@ -87,6 +88,8 @@ class Coder: return WholeFileCoder(main_model, io, **kwargs) elif edit_format == "whole-func": return WholeFileFunctionCoder(main_model, io, **kwargs) + elif edit_format == "single-whole-func": + return SingleWholeFileFunctionCoder(main_model, io, **kwargs) elif edit_format == "diff-func": return EditBlockFunctionCoder("list", main_model, io, **kwargs) elif edit_format == "diff-func-string": diff --git a/aider/coders/single_wholefile_func_coder.py b/aider/coders/single_wholefile_func_coder.py new file mode 100644 index 000000000..b6368311a --- /dev/null +++ b/aider/coders/single_wholefile_func_coder.py @@ -0,0 +1,119 @@ +import os + +from aider import diffs + +from ..dump import dump # noqa: F401 +from .base_coder import Coder +from .single_wholefile_func_prompts import SingleWholeFileFunctionPrompts + + +class SingleWholeFileFunctionCoder(Coder): + functions = [ + dict( + name="write_file", + description="write new content into the file", + parameters=dict( + type="object", + required=["explanation", "content"], + properties=dict( + explanation=dict( + type="string", + description=( + "Step by step plan for the changes to be made to the code (future" + " tense, markdown format)" + ), + ), + content=dict( + type="string", + description="Content to write to the file", + ), + ), + ), + ), + ] + + def __init__(self, *args, **kwargs): + self.gpt_prompts = SingleWholeFileFunctionPrompts() + super().__init__(*args, **kwargs) + + def update_cur_messages(self, content, edited): + if edited: + self.cur_messages += [ + dict(role="assistant", content=self.gpt_prompts.redacted_edit_message) + ] + else: + self.cur_messages += [dict(role="assistant", content=content)] + + def get_context_from_history(self, history): + context = "" + if history: + context += "# Context:\n" + for msg in history: + if msg["role"] == "user": + context += msg["role"].upper() + ": " + msg["content"] + "\n" + return context + + def render_incremental_response(self, final=False): + if self.partial_response_content: + return self.partial_response_content + + args = self.parse_partial_args() + + return str(args) + + if not args: + 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) + + return res + + def live_diffs(self, fname, content, final): + lines = content.splitlines(keepends=True) + + # ending an existing block + full_path = os.path.abspath(os.path.join(self.root, fname)) + + with open(full_path, "r") as f: + orig_lines = f.readlines() + + show_diff = diffs.diff_partial_update( + orig_lines, + lines, + final, + fname=fname, + ).splitlines() + + 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"') + + args = self.parse_partial_args() + if not args: + return + + content = args["content"] + path = self.get_inchat_relative_files()[0] + if self.allowed_to_edit(path, content): + return set([path]) + + return set() diff --git a/aider/coders/single_wholefile_func_prompts.py b/aider/coders/single_wholefile_func_prompts.py new file mode 100644 index 000000000..f97a91be9 --- /dev/null +++ b/aider/coders/single_wholefile_func_prompts.py @@ -0,0 +1,27 @@ +# flake8: noqa: E501 + +from .base_prompts import CoderPrompts + + +class SingleWholeFileFunctionPrompts(CoderPrompts): + main_system = """Act as an expert software developer. +Take requests for changes to the supplied code. +If the request is ambiguous, ask questions. + +Once you understand the request you MUST use the `write_file` function to update the file to make the changes. +""" + + system_reminder = """ +ONLY return code using the `write_file` function. +NEVER return code outside the `write_file` function. +""" + + files_content_prefix = "Here is the current content of the file:\n" + files_no_full_files = "I am not sharing any files yet." + + redacted_edit_message = "No changes are needed." + + # TODO: should this be present for using this with gpt-4? + repo_content_prefix = None + + # TODO: fix the chat history, except we can't keep the whole file