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.91.3.dev"
__version__ = "0.91.5.dev"
safe_version = __version__

try:
Expand Down
12 changes: 12 additions & 0 deletions aider/args.py
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,12 @@ def get_parser(default_config_files, git_root):
default="#0088ff",
help="Set the color for assistant output (default: #0088ff)",
)
group.add_argument(
"--show-speed",
action="store_true",
help="Show token processing and generation speed in usage report (default: False)",
default=False,
)
group.add_argument(
"--completion-menu-color",
metavar="COLOR",
Expand Down Expand Up @@ -856,6 +862,12 @@ def get_parser(default_config_files, git_root):
help="Never prompt for or attempt to install Playwright for web scraping (default: False).",
default=False,
)
group.add_argument(
"--disable-scraping",
action="store_true",
help="Disable automatic url scraping entirely web scraping (default: False).",
default=False,
)
group.add_argument(
"--file",
action="append",
Expand Down
81 changes: 5 additions & 76 deletions aider/coders/agent_coder.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,43 +34,7 @@
from aider.repo import ANY_GIT_ERROR

# Import tool modules for registry
# Import tool modules for registry
from aider.tools import (
command,
command_interactive,
delete_block,
delete_line,
delete_lines,
extract_lines,
finished,
git_branch,
git_diff,
git_log,
git_remote,
git_show,
git_status,
grep,
indent_lines,
insert_block,
list_changes,
load_skill,
ls,
make_editable,
make_readonly,
remove,
remove_skill,
replace_all,
replace_line,
replace_lines,
replace_text,
show_numbered_context,
thinking,
undo_change,
update_todo_list,
view,
view_files_matching,
view_files_with_symbol,
)
from aider.tools import TOOL_MODULES

from .base_coder import ChatChunks, Coder
from .editblock_coder import do_replace, find_original_update_blocks, find_similar_lines
Expand Down Expand Up @@ -178,42 +142,7 @@ def _build_tool_registry(self):
registry = {}

# Add tools that have been imported
tool_modules = [
command,
command_interactive,
delete_block,
delete_line,
delete_lines,
extract_lines,
finished,
git_branch,
git_diff,
git_log,
git_remote,
git_show,
git_status,
grep,
indent_lines,
insert_block,
list_changes,
load_skill,
ls,
make_editable,
make_readonly,
remove,
remove_skill,
replace_all,
replace_line,
replace_lines,
replace_text,
show_numbered_context,
thinking,
undo_change,
update_todo_list,
view,
view_files_matching,
view_files_with_symbol,
]
tool_modules = TOOL_MODULES

# Process agent configuration if provided
agent_config = self._get_agent_config()
Expand All @@ -229,7 +158,7 @@ def _build_tool_registry(self):
tools_excludelist.append("removeskill")

# Always include essential tools regardless of includelist/excludelist
essential_tools = {"makeeditable", "replacetext", "view", "finished"}
essential_tools = {"contextmanager", "replacetext", "finished"}
for module in tool_modules:
if hasattr(module, "Tool"):
tool_class = module.Tool
Expand Down Expand Up @@ -1069,8 +998,8 @@ def get_context_summary(self):
percentage = (total_tokens / max_input_tokens) * 100
result += f" ({percentage:.1f}% of limit)"
if percentage > 80:
result += "\n\n⚠️ **Context is getting full!** Remove non-essential files via:\n"
result += '- `[tool_call(Remove, file_path="path/to/large_file.ext")]`\n'
result += "\n\n⚠️ **Context is getting full!**\n"
result += "- Remove non-essential files via the `ContextManager` tool.\n"
result += "- Keep only essential files in context for best performance"
result += "\n</context>"

Expand Down
23 changes: 20 additions & 3 deletions aider/coders/base_coder.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
from aider.commands import Commands, SwitchCoder
from aider.exceptions import LiteLLMExceptions
from aider.helpers import coroutines
from aider.helpers.profiler import TokenProfiler
from aider.history import ChatSummary
from aider.io import ConfirmGroup, InputOutput
from aider.linter import Linter
Expand Down Expand Up @@ -375,6 +376,9 @@ def __init__(
self.message_tokens_sent = 0
self.message_tokens_received = 0

self.token_profiler = TokenProfiler(
enable_printing=getattr(args, "show_speed", False) if args else False
)
self.verbose = verbose
self.abs_fnames = set()
self.abs_read_only_fnames = set()
Expand Down Expand Up @@ -1637,7 +1641,7 @@ async def check_and_open_urls(self, exc, friendly_msg=None):

async def check_for_urls(self, inp: str) -> List[str]:
"""Check input for URLs and offer to add them to the chat."""
if not self.detect_urls:
if not self.detect_urls or self.args.disable_scraping:
return inp

# Exclude double quotes from the matched URL characters
Expand All @@ -1649,10 +1653,14 @@ async def check_for_urls(self, inp: str) -> List[str]:
if url not in self.rejected_urls:
url = url.rstrip(".',\"")
if await self.io.confirm_ask(
"Add URL to the chat?", subject=url, group=group, allow_never=True
"Add URL to the chat?",
subject=url,
group=group,
allow_never=True,
explicit_yes_required=self.args.yes_always_commands,
):
inp += "\n\n"
inp += await self.commands.cmd_web(url, return_content=True)
inp += await self.commands.do_run("web", url, return_content=True)
else:
self.rejected_urls.add(url)

Expand Down Expand Up @@ -2985,6 +2993,7 @@ async def send(self, messages, model=None, functions=None, tools=None):
self.partial_response_function_call = dict()

completion = None
self.token_profiler.start()

try:
hash_object, completion = await model.send_completion(
Expand All @@ -3010,6 +3019,7 @@ async def send(self, messages, model=None, functions=None, tools=None):
ex_info = LiteLLMExceptions().get_ex_info(err)
if ex_info.name == "ContextWindowExceededError":
# Still calculate costs for context window errors
self.token_profiler.on_error()
self.calculate_and_show_tokens_and_cost(messages, completion)
raise
except KeyboardInterrupt as kbi:
Expand Down Expand Up @@ -3100,6 +3110,7 @@ async def show_send_output_stream(self, completion):
try:
if chunk.choices[0].delta.tool_calls:
received_content = True
self.token_profiler.on_token()
for tool_call_chunk in chunk.choices[0].delta.tool_calls:
self.tool_reflection = True

Expand Down Expand Up @@ -3127,6 +3138,7 @@ async def show_send_output_stream(self, completion):
self.io.update_spinner_suffix(v)

received_content = True
self.token_profiler.on_token()
except AttributeError:
pass

Expand All @@ -3146,6 +3158,7 @@ async def show_send_output_stream(self, completion):
text += reasoning_content
self.got_reasoning_content = True
received_content = True
self.token_profiler.on_token()
self.io.update_spinner_suffix(reasoning_content)
self.partial_response_reasoning_content += reasoning_content

Expand All @@ -3158,6 +3171,7 @@ async def show_send_output_stream(self, completion):

text += content
received_content = True
self.token_profiler.on_token()
self.io.update_spinner_suffix(content)
except AttributeError:
pass
Expand Down Expand Up @@ -3397,6 +3411,9 @@ def calculate_and_show_tokens_and_cost(self, messages, completion=None):
if cache_hit_tokens:
tokens_report += f", {format_tokens(cache_hit_tokens)} cache hit"
tokens_report += f", {format_tokens(self.message_tokens_received)} received."
tokens_report = self.token_profiler.add_to_usage_report(
tokens_report, self.message_tokens_sent, self.message_tokens_received
)

if not self.main_model.info.get("input_cost_per_token"):
self.usage_report = tokens_report
Expand Down
26 changes: 14 additions & 12 deletions aider/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ def get_commands(self):
commands = [f"/{cmd}" for cmd in registry_commands]
return sorted(commands)

async def do_run(self, cmd_name, args):
async def do_run(self, cmd_name, args, **kwargs):
# Execute command using registry
command_class = CommandRegistry.get_command(cmd_name)
if not command_class:
Expand All @@ -115,17 +115,19 @@ async def do_run(self, cmd_name, args):
self.cmd_running_event.clear() # Command is running
try:
# Generate a spreadable kwargs dict with all relevant Commands attributes
kwargs = {
"original_read_only_fnames": self.original_read_only_fnames,
"voice_language": self.voice_language,
"voice_format": self.voice_format,
"voice_input_device": self.voice_input_device,
"verify_ssl": self.verify_ssl,
"parser": self.parser,
"verbose": self.verbose,
"editor": self.editor,
"system_args": self.args,
}
kwargs.update(
{
"original_read_only_fnames": self.original_read_only_fnames,
"voice_language": self.voice_language,
"voice_format": self.voice_format,
"voice_input_device": self.voice_input_device,
"verify_ssl": self.verify_ssl,
"parser": self.parser,
"verbose": self.verbose,
"editor": self.editor,
"system_args": self.args,
}
)

return await CommandRegistry.execute(
cmd_name,
Expand Down
3 changes: 3 additions & 0 deletions aider/commands/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
)
from .utils.registry import CommandRegistry
from .voice import VoiceCommand
from .weak_model import WeakModelCommand
from .web import WebCommand

# Register commands
Expand Down Expand Up @@ -110,6 +111,7 @@
CommandRegistry.register(ReadOnlyStubCommand)
CommandRegistry.register(AddCommand)
CommandRegistry.register(ModelCommand)
CommandRegistry.register(WeakModelCommand)
CommandRegistry.register(WebCommand)
CommandRegistry.register(LintCommand)
CommandRegistry.register(TestCommand)
Expand Down Expand Up @@ -217,6 +219,7 @@ def __init__(self, *args, **kwargs):
"ReadOnlyStubCommand",
"AddCommand",
"ModelCommand",
"WeakModelCommand",
"WebCommand",
"LintCommand",
"TestCommand",
Expand Down
27 changes: 18 additions & 9 deletions aider/commands/load.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from aider.commands.utils.base_command import BaseCommand
from aider.commands.utils.helpers import format_command_result
from aider.commands.utils.save_load_manager import SaveLoadManager


class LoadCommand(BaseCommand):
Expand All @@ -15,12 +16,13 @@ async def execute(cls, io, coder, args, **kwargs):
io.tool_error("Please provide a filename containing commands to load.")
return format_command_result(io, "load", "No filename provided")

manager = SaveLoadManager(coder, io)

try:
with open(args.strip(), "r", encoding=io.encoding, errors="replace") as f:
commands = f.readlines()
except FileNotFoundError:
io.tool_error(f"File not found: {args}")
return format_command_result(io, "load", f"File not found: {args}")
commands = manager.load_commands(args.strip())
except FileNotFoundError as e:
io.tool_error(str(e))
return format_command_result(io, "load", str(e))
except Exception as e:
io.tool_error(f"Error reading file: {e}")
return format_command_result(io, "load", f"Error reading file: {e}")
Expand All @@ -34,6 +36,7 @@ async def execute(cls, io, coder, args, **kwargs):

commands_instance = Commands(io, coder)

should_raise_at_end = None
for cmd in commands:
cmd = cmd.strip()
if not cmd or cmd.startswith("#"):
Expand All @@ -45,21 +48,27 @@ async def execute(cls, io, coder, args, **kwargs):
except Exception as e:
# Handle SwitchCoder exception specifically
if type(e).__name__ == "SwitchCoder":
io.tool_error(
f"Command '{cmd}' is only supported in interactive mode, skipping."
)
# SwitchCoder is raised when switching between coder types (e.g., /architect, /ask).
# This is expected behavior, not an error. But this gets in the way when running `/load` so we
# ignore it and continue processing remaining commands.
should_raise_at_end = e
continue
else:
# Re-raise other exceptions
raise

if should_raise_at_end:
raise should_raise_at_end

return format_command_result(
io, "load", f"Loaded and executed commands from {args.strip()}"
)

@classmethod
def get_completions(cls, io, coder, args) -> List[str]:
"""Get completion options for load command."""
return []
manager = SaveLoadManager(coder, io)
return manager.list_files()

@classmethod
def get_help(cls) -> str:
Expand Down
Loading
Loading