diff --git a/aider/__init__.py b/aider/__init__.py index 8ef542b15ff..fbbf12541b9 100644 --- a/aider/__init__.py +++ b/aider/__init__.py @@ -1,6 +1,6 @@ from packaging import version -__version__ = "0.88.37.dev" +__version__ = "0.88.38.dev" safe_version = __version__ try: diff --git a/aider/args.py b/aider/args.py index 928a7328148..91137539a9c 100644 --- a/aider/args.py +++ b/aider/args.py @@ -965,6 +965,11 @@ def get_parser(default_config_files, git_root): " specified, a default command for your OS may be used." ), ) + group.add_argument( + "--command-prefix", + default=None, + help="Specify a command prefix for all commands (useful for sandboxing)", + ) group.add_argument( "--detect-urls", action=argparse.BooleanOptionalAction, diff --git a/aider/coders/base_coder.py b/aider/coders/base_coder.py index 52b5970d612..c55d4ddb69b 100755 --- a/aider/coders/base_coder.py +++ b/aider/coders/base_coder.py @@ -685,7 +685,10 @@ def show_pretty(self): def get_abs_fnames_content(self): # Sort files by last modified time (earliest first, latest last) - sorted_fnames = sorted(self.abs_fnames, key=lambda fname: os.path.getmtime(fname)) + sorted_fnames = sorted( + list(filter(lambda f: os.path.exists(f), self.abs_fnames)), + key=lambda fname: os.path.getmtime(fname), + ) for fname in sorted_fnames: content = self.io.read_text(fname) @@ -743,8 +746,9 @@ def get_files_content(self, fnames=None): file_times = [] for fname in fnames: try: - mtime = os.path.getmtime(fname) - file_times.append((fname, mtime)) + if os.path.exists(fname): + mtime = os.path.getmtime(fname) + file_times.append((fname, mtime)) except OSError: # Skip files that can't be accessed continue @@ -840,7 +844,10 @@ def get_files_content(self, fnames=None): def get_read_only_files_content(self): prompt = "" # Sort read-only files by last modified time (earliest first, latest last) - sorted_fnames = sorted(self.abs_read_only_fnames, key=lambda fname: os.path.getmtime(fname)) + sorted_fnames = sorted( + list(filter(lambda f: os.path.exists(f), self.abs_read_only_fnames)), + key=lambda fname: os.path.getmtime(fname), + ) # Handle regular read-only files for fname in sorted_fnames: @@ -890,7 +897,8 @@ def get_read_only_files_content(self): # Sort stub files by last modified time (earliest first, latest last) sorted_stub_fnames = sorted( - self.abs_read_only_stubs_fnames, key=lambda fname: os.path.getmtime(fname) + list(filter(lambda f: os.path.exists(f), self.abs_read_only_stubs_fnames)), + key=lambda fname: os.path.getmtime(fname), ) # Handle stub files @@ -3830,6 +3838,10 @@ async def handle_shell_commands(self, commands_str, group): if not command or command.startswith("#"): continue + if command and getattr(self.args, "command_prefix", None): + command_prefix = getattr(self.args, "command_prefix", None) + command = f"{command_prefix} {command}" + self.io.tool_output() self.io.tool_output(f"Running {command}") # Add the command to input history diff --git a/aider/commands.py b/aider/commands.py index 0f3b63205d7..73a702334ed 100644 --- a/aider/commands.py +++ b/aider/commands.py @@ -1149,7 +1149,7 @@ async def cmd_drop(self, args=""): matched_files = [ self.coder.get_rel_fname(f) for f in self.coder.abs_fnames - if expanded_word in f + if self.coder.abs_root_path(expanded_word) in f ] if not matched_files: diff --git a/aider/helpers/requests.py b/aider/helpers/requests.py index e61c1728cde..230f89a1012 100644 --- a/aider/helpers/requests.py +++ b/aider/helpers/requests.py @@ -7,17 +7,19 @@ def thought_signature(model, messages): for msg in messages: if "tool_calls" in msg: tool_calls = msg["tool_calls"] - for call in tool_calls: - if not call: - continue - - # Check if thought signature is missing in extra_content.google.thought_signature - if "provider_specific_fields" not in call: - call["provider_specific_fields"] = {} - if "thought_signature" not in call["provider_specific_fields"]: - call["provider_specific_fields"][ - "thought_signature" - ] = "skip_thought_signature_validator" + + if tool_calls: + for call in tool_calls: + if not call: + continue + + # Check if thought signature is missing in extra_content.google.thought_signature + if "provider_specific_fields" not in call: + call["provider_specific_fields"] = {} + if "thought_signature" not in call["provider_specific_fields"]: + call["provider_specific_fields"][ + "thought_signature" + ] = "skip_thought_signature_validator" if "function_call" in msg: call = msg["function_call"] diff --git a/aider/tools/command.py b/aider/tools/command.py index 7c539c7f9fe..cfed959bc65 100644 --- a/aider/tools/command.py +++ b/aider/tools/command.py @@ -32,6 +32,9 @@ async def execute(cls, coder, command_string): # Ask for confirmation before executing. # allow_never=True enables the 'Always' option. # confirm_ask handles remembering the 'Always' choice based on the subject. + if command_string and getattr(coder.args, "command_prefix", None): + command_prefix = getattr(coder.args, "command_prefix", None) + command_string = f"{command_prefix} {command_string}" confirmed = ( True diff --git a/aider/tools/command_interactive.py b/aider/tools/command_interactive.py index 5d25a36f1af..1ccb621f7e7 100644 --- a/aider/tools/command_interactive.py +++ b/aider/tools/command_interactive.py @@ -31,6 +31,10 @@ async def execute(cls, coder, command_string): Execute an interactive shell command using run_cmd (which uses pexpect/PTY). """ try: + if command_string and getattr(coder.args, "command_prefix", None): + command_prefix = getattr(coder.args, "command_prefix", None) + command_string = f"{command_prefix} {command_string}" + confirmed = ( True if coder.skip_cli_confirmations