Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion aider/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from packaging import version

__version__ = "0.88.27.dev"
__version__ = "0.88.28.dev"
safe_version = __version__

try:
Expand Down
23 changes: 12 additions & 11 deletions aider/coders/agent_coder.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ def __init__(self, *args, **kwargs):

# Initialize tool registry
self.args = kwargs.get("args")
self._tool_registry = self._build_tool_registry()
self.tool_registry = self._build_tool_registry()

# Track files added during current exploration
self.files_added_in_exploration = set()
Expand Down Expand Up @@ -201,8 +201,9 @@ def _build_tool_registry(self):
# Always include essential tools regardless of includelist/excludelist
essential_tools = {"makeeditable", "replacetext", "view", "finished"}
for module in tool_modules:
if hasattr(module, "NORM_NAME") and hasattr(module, "process_response"):
tool_name = module.NORM_NAME
if hasattr(module, "Tool"):
tool_class = module.Tool
tool_name = tool_class.NORM_NAME

# Check if tool should be included based on configuration
should_include = True
Expand All @@ -220,7 +221,7 @@ def _build_tool_registry(self):
should_include = False

if should_include:
registry[tool_name] = module
registry[tool_name] = tool_class

return registry

Expand Down Expand Up @@ -267,9 +268,9 @@ def get_local_tool_schemas(self):
schemas = []

# Get schemas from the tool registry
for tool_module in self._tool_registry.values():
if hasattr(tool_module, "schema"):
schemas.append(tool_module.schema)
for tool_module in self.tool_registry.values():
if hasattr(tool_module, "SCHEMA"):
schemas.append(tool_module.SCHEMA)

return schemas

Expand Down Expand Up @@ -324,8 +325,8 @@ async def _execute_local_tool_calls(self, tool_calls_list):
tasks = []

# Use the tool registry for execution
if norm_tool_name in self._tool_registry:
tool_module = self._tool_registry[norm_tool_name]
if norm_tool_name in self.tool_registry:
tool_module = self.tool_registry[norm_tool_name]
for params in parsed_args_list:
# Use the process_response function from the tool module
result = tool_module.process_response(self, params)
Expand Down Expand Up @@ -1137,8 +1138,8 @@ async def _execute_tool_with_registry(self, norm_tool_name, params):
str: Result message
"""
# Check if tool exists in registry
if norm_tool_name in self._tool_registry:
tool_module = self._tool_registry[norm_tool_name]
if norm_tool_name in self.tool_registry:
tool_module = self.tool_registry[norm_tool_name]
try:
# Use the process_response function from the tool module
result = tool_module.process_response(self, params)
Expand Down
95 changes: 30 additions & 65 deletions aider/coders/base_coder.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
from aider.repomap import RepoMap
from aider.run_cmd import run_cmd
from aider.sessions import SessionManager
from aider.tools.utils.output import print_tool_response
from aider.utils import format_tokens, is_image_file

from ..dump import dump # noqa: F401
Expand Down Expand Up @@ -1125,7 +1126,7 @@ async def _run_linear(self, with_message=None, preproc=True):
return self.partial_response_content

user_message = None
await self.io.cancel_task_streams()
await self.io.stop_task_streams()

while True:
try:
Expand All @@ -1150,22 +1151,17 @@ async def _run_linear(self, with_message=None, preproc=True):
await self.auto_save_session()

except KeyboardInterrupt:
if self.io.input_task:
self.io.set_placeholder("")
await self.io.cancel_input_task()

if self.io.output_task:
await self.io.cancel_output_task()
self.io.stop_spinner()

self.io.set_placeholder("")
self.io.stop_spinner()
self.keyboard_interrupt()
await self.io.stop_task_streams()
except (asyncio.CancelledError, IndexError):
pass

except EOFError:
return
finally:
await self.io.cancel_task_streams()
await self.io.stop_task_streams()

async def _run_parallel(self, with_message=None, preproc=True):
try:
Expand All @@ -1180,7 +1176,7 @@ async def _run_parallel(self, with_message=None, preproc=True):
self.user_message = ""

# Cancel any existing tasks
await self.io.cancel_task_streams()
await self.io.stop_task_streams()

# Start the input and output tasks
input_task = asyncio.create_task(self.input_task(preproc))
Expand Down Expand Up @@ -1221,13 +1217,13 @@ async def _run_parallel(self, with_message=None, preproc=True):
pass

# Ensure IO tasks are properly cancelled
await self.io.cancel_task_streams()
await self.io.stop_task_streams()

await self.auto_save_session()
except EOFError:
return
finally:
await self.io.cancel_task_streams()
await self.io.stop_task_streams()

async def input_task(self, preproc):
"""
Expand All @@ -1253,11 +1249,11 @@ async def input_task(self, preproc):
await self.auto_save_session()
else:
self.user_message = ""
await self.io.cancel_task_streams()
await self.io.stop_task_streams()

except (asyncio.CancelledError, KeyboardInterrupt):
self.user_message = ""
await self.io.cancel_task_streams()
await self.io.stop_task_streams()

# Check if we should show announcements
if (
Expand All @@ -1284,7 +1280,7 @@ async def input_task(self, preproc):
except KeyboardInterrupt:
self.io.set_placeholder("")
self.keyboard_interrupt()
await self.io.cancel_task_streams()
await self.io.stop_task_streams()
except (SwitchCoder, SystemExit):
raise
except Exception as e:
Expand Down Expand Up @@ -1324,16 +1320,25 @@ async def output_task(self, preproc):
await self.io.output_task
raise exception

self.io.tool_error(f"Error during generation: {exception}")
if self.verbose:
traceback.print_exception(
type(exception), exception, exception.__traceback__
)

# Stop spinner when processing task completes
self.io.stop_spinner()

# And stop monitoring the output task
await self.io.stop_output_task()

await self.auto_save_session()
await asyncio.sleep(0.01) # Small yield to prevent tight loop

except KeyboardInterrupt:
self.io.stop_spinner()
self.keyboard_interrupt()
await self.io.cancel_task_streams()
await self.io.stop_task_streams()
except (SwitchCoder, SystemExit):
raise
except Exception as e:
Expand Down Expand Up @@ -2355,54 +2360,14 @@ def _print_tool_call_info(self, server_tool_calls):

for server, tool_calls in server_tool_calls.items():
for tool_call in tool_calls:
color_start = "[blue]" if self.pretty else ""
color_end = "[/blue]" if self.pretty else ""

self.io.tool_output(
f"{color_start}Tool Call:{color_end} {server.name} • {tool_call.function.name}"
)
# Parse and format arguments as headers with values
if tool_call.function.arguments:
# Only do JSON unwrapping for tools containing "replace" in their name
if tool_call.get("function", {}).get("name") is not None and (
"replace" in tool_call.function.name.lower()
or "insert" in tool_call.function.name.lower()
or "update" in tool_call.function.name.lower()
):
try:
args_dict = json.loads(tool_call.function.arguments)
first_key = True
for key, value in args_dict.items():
# Convert explicit \\n sequences to actual newlines using regex
# Only match \\n that is not preceded by any other backslashes
if isinstance(value, str):
value = re.sub(r"(?<!\\)\\n", "\n", value)
# Add extra newline before first key/header
if first_key:
self.io.tool_output("\n")
first_key = False
self.io.tool_output(f"{color_start}{key}:{color_end}")
# Split the value by newlines and output each line separately
if isinstance(value, str):
for line in value.split("\n"):
self.io.tool_output(f"{line}")
else:
self.io.tool_output(f"{str(value)}")
self.io.tool_output("")
except json.JSONDecodeError:
# If JSON parsing fails, show raw arguments
raw_args = tool_call.function.arguments
self.io.tool_output(f"{color_start}Arguments:{color_end} {raw_args}")
else:
# For non-replace tools, show raw arguments
raw_args = tool_call.function.arguments
self.io.tool_output(f"{color_start}Arguments:{color_end} {raw_args}")

if self.verbose:
self.io.tool_output(f"Tool ID: {tool_call.id}")
self.io.tool_output(f"Tool type: {tool_call.type}")

self.io.tool_output("\n")
if hasattr(self, "tool_registry") and self.tool_registry.get(
tool_call.function.name.lower(), None
):
self.tool_registry.get(tool_call.function.name.lower()).format_output(
coder=self, mcp_server=server, tool_response=tool_call
)
else:
print_tool_response(coder=self, mcp_server=server, tool_response=tool_call)

def _gather_server_tool_calls(self, tool_calls):
"""Collect all tool calls grouped by server.
Expand Down
10 changes: 5 additions & 5 deletions aider/io.py
Original file line number Diff line number Diff line change
Expand Up @@ -966,7 +966,7 @@ def get_continuation(width, line_number, is_soft_wrap):
self.user_input(inp)
return inp

async def cancel_input_task(self):
async def stop_input_task(self):
if self.input_task:
input_task = self.input_task
self.input_task = None
Expand All @@ -983,7 +983,7 @@ async def cancel_input_task(self):
):
pass

async def cancel_output_task(self):
async def stop_output_task(self):
if self.output_task:
output_task = self.output_task
self.output_task = None
Expand All @@ -1000,9 +1000,9 @@ async def cancel_output_task(self):
):
pass

async def cancel_task_streams(self):
input_task = asyncio.create_task(self.cancel_input_task())
output_task = asyncio.create_task(self.cancel_output_task())
async def stop_task_streams(self):
input_task = asyncio.create_task(self.stop_input_task())
output_task = asyncio.create_task(self.stop_output_task())

await asyncio.wait([input_task, output_task], return_when=asyncio.ALL_COMPLETED)

Expand Down
2 changes: 1 addition & 1 deletion aider/repomap.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
normalize_vector,
)
from aider.special import filter_important_files
from aider.tools.tool_utils import ToolError
from aider.tools.utils.helpers import ToolError

# tree_sitter is throwing a FutureWarning
warnings.simplefilter("ignore", category=FutureWarning)
Expand Down
4 changes: 4 additions & 0 deletions aider/resources/model-settings.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1890,6 +1890,7 @@
system_prompt_prefix: "Formatting re-enabled. "
accepts_settings: ["reasoning_effort"]
examples_as_sys_msg: true
use_temperature: false

- name: gpt-5-2025-08-07
edit_format: diff
Expand Down Expand Up @@ -1985,6 +1986,7 @@
system_prompt_prefix: "Formatting re-enabled. "
accepts_settings: ["reasoning_effort"]
examples_as_sys_msg: true
use_temperature: false

- name: azure/gpt-5-2025-08-07
edit_format: diff
Expand Down Expand Up @@ -2080,6 +2082,7 @@
system_prompt_prefix: "Formatting re-enabled. "
accepts_settings: ["reasoning_effort"]
examples_as_sys_msg: true
use_temperature: false

- name: openai/gpt-5-2025-08-07
edit_format: diff
Expand Down Expand Up @@ -2175,6 +2178,7 @@
system_prompt_prefix: "Formatting re-enabled. "
accepts_settings: ["reasoning_effort"]
examples_as_sys_msg: true
use_temperature: false

- name: openrouter/openai/gpt-5-2025-08-07
edit_format: diff
Expand Down
Loading
Loading