From 4b3b41bad1848e2670335cb2b2be136f90fdd318 Mon Sep 17 00:00:00 2001 From: Dustin Washington Date: Mon, 8 Dec 2025 23:59:18 -0500 Subject: [PATCH 1/6] Bump Version --- aider/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aider/__init__.py b/aider/__init__.py index 2f616d59bae..2c8d3aa69ce 100644 --- a/aider/__init__.py +++ b/aider/__init__.py @@ -1,6 +1,6 @@ from packaging import version -__version__ = "0.89.2.dev" +__version__ = "0.89.3.dev" safe_version = __version__ try: From 59ee5eceb3d0e83cf2356809780d8f36311c8bc0 Mon Sep 17 00:00:00 2001 From: Dustin Washington Date: Tue, 9 Dec 2025 00:14:25 -0500 Subject: [PATCH 2/6] #239: Fix cache control to account for limits by only caching last and penultimate messages --- aider/coders/chat_chunks.py | 17 ++++++++++++++++- aider/models.py | 12 ++++++++++-- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/aider/coders/chat_chunks.py b/aider/coders/chat_chunks.py index 21c49f55087..ce2b547c152 100644 --- a/aider/coders/chat_chunks.py +++ b/aider/coders/chat_chunks.py @@ -62,10 +62,25 @@ def add_cache_control_headers(self): # The history is ephemeral on its own. self.add_cache_control(self.done) + # Per this: https://github.com/BerriAI/litellm/issues/10226 + # The first and second to last messages are cache optimal + # Since caches are also written to incrementally and you need + # the past and current states to properly append and gain + # efficiencies/savings in cache writing def add_cache_control(self, messages): - if not messages: + if not messages or len(messages) < 2: return + content = messages[-2]["content"] + if type(content) is str: + content = dict( + type="text", + text=content, + ) + content["cache_control"] = {"type": "ephemeral"} + + messages[-2]["content"] = [content] + content = messages[-1]["content"] if type(content) is str: content = dict( diff --git a/aider/models.py b/aider/models.py index 81a34efaec6..082e4300f67 100644 --- a/aider/models.py +++ b/aider/models.py @@ -977,11 +977,19 @@ async def send_completion( dump(kwargs) kwargs["messages"] = messages - # Cache System Prompts When Possible + # Per this: https://github.com/BerriAI/litellm/issues/10226 + # The first and second to last messages are cache optimal + # Since caches are also written to incrementally and you need + # the past and current states to properly append and gain + # efficiencies/savings in cache writing kwargs["cache_control_injection_points"] = [ { "location": "message", - "role": "system", + "index": -1, + }, + { + "location": "message", + "index": -2, }, ] From 5016fe1524b4f54d993c60d8828c78d66b28d818 Mon Sep 17 00:00:00 2001 From: Dustin Washington Date: Tue, 9 Dec 2025 00:19:03 -0500 Subject: [PATCH 3/6] #240: Valid, make the blue lighter --- aider/io.py | 2 +- aider/tools/utils/output.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/aider/io.py b/aider/io.py index 0e7bd5d6b78..79e8cf85fbd 100644 --- a/aider/io.py +++ b/aider/io.py @@ -296,7 +296,7 @@ class InputOutput: bell_on_next_input = False notifications_command = None encoding = "utf-8" - VALID_STYLES = {"bold", "red", "green", "blue", "orange"} + VALID_STYLES = {"bold", "red", "green", "blue", "bright_cyan"} VALID_OPEN_TAG_PATTERN = re.compile( r"\\\[(" + "|".join(re.escape(s) for s in VALID_STYLES) + r")\]" ) diff --git a/aider/tools/utils/output.py b/aider/tools/utils/output.py index 2e21545e8f1..b038de924db 100644 --- a/aider/tools/utils/output.py +++ b/aider/tools/utils/output.py @@ -112,7 +112,7 @@ def color_markers(coder): Args: coder: An instance of base_coder """ - color_start = "[blue]" if coder.pretty else "" - color_end = "[/blue]" if coder.pretty else "" + color_start = "[bright_cyan]" if coder.pretty else "" + color_end = "[/bright_cyan]" if coder.pretty else "" return color_start, color_end From 828d7ab7c40a4c3facd9699dfc1f8d3052c704fd Mon Sep 17 00:00:00 2001 From: Dustin Washington Date: Tue, 9 Dec 2025 00:33:11 -0500 Subject: [PATCH 4/6] #59: Add instructions on the docker run --- README.md | 17 +++++++++++++++++ docker/Dockerfile | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 69e100718cc..e70585822f5 100644 --- a/README.md +++ b/README.md @@ -100,6 +100,23 @@ OPENROUTER_API_KEY="..." DEEPSEEK_API_KEY="..." ``` +### Run Program + +If you are in the directory with your .aider.conf.yml file, then simply running `aider-ce` or `cecli` will start the agent with your configuration. If you want additional sandboxing, we publish a docker container that can be ran as follows: + +```bash +docker pull dustinwashington/aider-ce +docker run \ + -it \ + --user $(id -u):$(id -g) \ + --volume $(pwd):/app dustinwashington/aider-ce \ + --volume $(pwd)/.aider.conf.yml:/.aider.conf.yml \ + --volume $(pwd)/.aider.env:/.aider/.env \ + --config /app/.aider.conf.yml +``` + +This command will make sure all commands ran by the coding agent happen in context of the docker container to protect the house file system for any infamous agentic mishap + ## Project Roadmap/Goals The current priorities are to improve core capabilities and user experience of the Aider project diff --git a/docker/Dockerfile b/docker/Dockerfile index 2778dd7fcfe..7e223d07ae7 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -70,4 +70,4 @@ RUN uv pip install . && \ # Switch to appuser USER appuser -ENTRYPOINT ["/venv/bin/aider"] +ENTRYPOINT ["/venv/bin/aider-ce"] From 889247db43cb93103884c06af14918e6a4f2dfb0 Mon Sep 17 00:00:00 2001 From: Dustin Washington Date: Tue, 9 Dec 2025 01:05:42 -0500 Subject: [PATCH 5/6] #215: Use semicolons as command separators, not lines --- aider/coders/base_coder.py | 2 +- aider/run_cmd.py | 26 +++++++++++++++++++++++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/aider/coders/base_coder.py b/aider/coders/base_coder.py index 14146f8afb7..6e6f758af8b 100755 --- a/aider/coders/base_coder.py +++ b/aider/coders/base_coder.py @@ -3804,7 +3804,7 @@ async def run_shell_commands(self): self.commands.cmd_running = False async def handle_shell_commands(self, commands_str, group): - commands = commands_str.strip().splitlines() + commands = commands_str.strip().split(";") command_count = sum( 1 for cmd in commands if cmd.strip() and not cmd.strip().startswith("#") ) diff --git a/aider/run_cmd.py b/aider/run_cmd.py index df92503d7e4..fa13ec69530 100644 --- a/aider/run_cmd.py +++ b/aider/run_cmd.py @@ -60,7 +60,7 @@ def run_cmd_subprocess(command, verbose=False, cwd=None, encoding=sys.stdout.enc print("Parent process:", parent_process) process = subprocess.Popen( - command, + process_multiline_command(command), stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, @@ -131,3 +131,27 @@ def output_callback(b): except (pexpect.ExceptionPexpect, TypeError, ValueError) as e: error_msg = f"Error running command {command}: {e}" return 1, error_msg + + +def process_multiline_command(command_string): + """ + Removes lines that end with a single backslash used for line continuation. + """ + lines = command_string.splitlines() + processed_lines = [] + + for line in lines: + # rstrip() removes trailing whitespace, including newlines in the original splitlines context. + # We check if the line, stripped of whitespace, ends with a single backslash. + stripped_line = line.rstrip() + + # We need to distinguish between 'foo\' and 'foo\\' (literal backslash) + if stripped_line.endswith("\\") and not stripped_line.endswith("\\\\"): + # This line ends in an unescaped backslash (line continuation), so we skip it. + continue + else: + processed_lines.append(line) + + # Join the remaining lines with the appropriate newline character + # Use '\n'.join for consistent Unix-style newlines, or os.linesep + return "\n".join(processed_lines) From d71219f97388c1465eeb25262b1a908112aa9c3a Mon Sep 17 00:00:00 2001 From: Dustin Washington Date: Tue, 9 Dec 2025 01:12:39 -0500 Subject: [PATCH 6/6] Will throw away some lines unnecessarily --- aider/run_cmd.py | 26 +------------------------- 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/aider/run_cmd.py b/aider/run_cmd.py index fa13ec69530..df92503d7e4 100644 --- a/aider/run_cmd.py +++ b/aider/run_cmd.py @@ -60,7 +60,7 @@ def run_cmd_subprocess(command, verbose=False, cwd=None, encoding=sys.stdout.enc print("Parent process:", parent_process) process = subprocess.Popen( - process_multiline_command(command), + command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, @@ -131,27 +131,3 @@ def output_callback(b): except (pexpect.ExceptionPexpect, TypeError, ValueError) as e: error_msg = f"Error running command {command}: {e}" return 1, error_msg - - -def process_multiline_command(command_string): - """ - Removes lines that end with a single backslash used for line continuation. - """ - lines = command_string.splitlines() - processed_lines = [] - - for line in lines: - # rstrip() removes trailing whitespace, including newlines in the original splitlines context. - # We check if the line, stripped of whitespace, ends with a single backslash. - stripped_line = line.rstrip() - - # We need to distinguish between 'foo\' and 'foo\\' (literal backslash) - if stripped_line.endswith("\\") and not stripped_line.endswith("\\\\"): - # This line ends in an unescaped backslash (line continuation), so we skip it. - continue - else: - processed_lines.append(line) - - # Join the remaining lines with the appropriate newline character - # Use '\n'.join for consistent Unix-style newlines, or os.linesep - return "\n".join(processed_lines)