mirror of
https://github.com/Aider-AI/aider.git
synced 2025-06-01 10:14:59 +00:00
Merge branch 'main' into feature/litellm-mcp
This commit is contained in:
commit
374d69d428
45 changed files with 2371 additions and 1045 deletions
25
HISTORY.md
25
HISTORY.md
|
@ -2,6 +2,28 @@
|
|||
|
||||
### main branch
|
||||
|
||||
- Added support for `qwen3-235b` models, including `openrouter/qwen/qwen3-235b-a22b`.
|
||||
- Added support for `gemini-2.5-pro-preview-05-06` models.
|
||||
- Added repomap support for OCaml and OCaml interface files, by Andrey Popp.
|
||||
- Introduced `--attribute-co-authored-by` option to add co-author trailer to commit messages, by Andrew Grigorev.
|
||||
- Updated Gemini model aliases (e.g., `gemini`, `gemini-2.5-pro`) to point to the `05-06` preview versions.
|
||||
- Marked Gemini 2.5 Pro preview models as `overeager` by default.
|
||||
- Updated the default weak model for Gemini 2.5 Pro models to `gemini/gemini-2.5-flash-preview-04-17`.
|
||||
- Corrected `gemini-2.5-pro-exp-03-25` model settings to reflect its lack of support for `thinking_budget`.
|
||||
- Ensured model-specific system prompt prefixes are placed on a new line before the main system prompt.
|
||||
- Added tracking of total tokens sent and received, now included in benchmark statistics.
|
||||
- Automatically fetch model parameters (context window, pricing) for OpenRouter models directly from their website, by Stefan Hladnik.
|
||||
- Enabled support for `thinking_tokens` and `reasoning_effort` parameters for OpenRouter models.
|
||||
- Improved cost calculation using `litellm.completion_cost` where available.
|
||||
- Added model settings for `openrouter/google/gemini-2.5-pro-preview-03-25`.
|
||||
- Added `--disable-playwright` flag to prevent Playwright installation prompts and usage, by Andrew Grigorev.
|
||||
- The `aider scrape` command-line tool will now use Playwright for web scraping if it is available, by Jon Keys.
|
||||
- Fixed linter command execution on Windows by adopting `oslex` for argument quoting, by Titusz Pan.
|
||||
- Improved cross-platform display of shell commands by using `oslex` for robust argument quoting, by Titusz Pan.
|
||||
- Aider wrote 46% of the code in this release.
|
||||
|
||||
### Aider v0.82.3
|
||||
|
||||
- Add support for `gemini-2.5-flash-preview-04-17` models.
|
||||
- Improved robustness of edit block parsing when filenames start with backticks or fences.
|
||||
- Add new `udiff-simple` edit format, for Gemini 2.5 Pro.
|
||||
|
@ -10,9 +32,8 @@
|
|||
- Fix parsing of diffs for newly created files (`--- /dev/null`).
|
||||
- Add markdown syntax highlighting support when editing multi-line commit messages via `/commit`, by Kay Gosho.
|
||||
- Set Gemini 2.5 Pro models to use the `overeager` prompt setting by default.
|
||||
- Add common file types (`.svg`, `.pdf`) and IDE directories (`.idea/`, `.vscode/`, etc.) to the default list of ignored files for AI comment scanning (`--watch`).
|
||||
- Add common file types (`.svg`, `.pdf`) to the default list of ignored files for AI comment scanning (`--watch`).
|
||||
- Skip scanning files larger than 1MB for AI comments (`--watch`).
|
||||
- Aider wrote 67% of the code in this release.
|
||||
|
||||
### Aider v0.82.2
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ cog.out(text)
|
|||
<a href="https://github.com/Aider-AI/aider/stargazers"><img alt="GitHub Stars" title="Total number of GitHub stars the Aider project has received"
|
||||
src="https://img.shields.io/github/stars/Aider-AI/aider?style=flat-square&logo=github&color=f1c40f&labelColor=555555"/></a>
|
||||
<a href="https://pypi.org/project/aider-chat/"><img alt="PyPI Downloads" title="Total number of installations via pip from PyPI"
|
||||
src="https://img.shields.io/badge/📦%20Installs-2.1M-2ecc71?style=flat-square&labelColor=555555"/></a>
|
||||
src="https://img.shields.io/badge/📦%20Installs-2.2M-2ecc71?style=flat-square&labelColor=555555"/></a>
|
||||
<img alt="Tokens per week" title="Number of tokens processed weekly by Aider users"
|
||||
src="https://img.shields.io/badge/📈%20Tokens%2Fweek-15B-3498db?style=flat-square&labelColor=555555"/>
|
||||
<a href="https://openrouter.ai/#options-menu"><img alt="OpenRouter Ranking" title="Aider's ranking among applications on the OpenRouter platform"
|
||||
|
|
|
@ -427,14 +427,20 @@ def get_parser(default_config_files, git_root):
|
|||
group.add_argument(
|
||||
"--attribute-author",
|
||||
action=argparse.BooleanOptionalAction,
|
||||
default=True,
|
||||
help="Attribute aider code changes in the git author name (default: True)",
|
||||
default=None,
|
||||
help=(
|
||||
"Attribute aider code changes in the git author name (default: True). If explicitly set"
|
||||
" to True, overrides --attribute-co-authored-by precedence."
|
||||
),
|
||||
)
|
||||
group.add_argument(
|
||||
"--attribute-committer",
|
||||
action=argparse.BooleanOptionalAction,
|
||||
default=True,
|
||||
help="Attribute aider commits in the git committer name (default: True)",
|
||||
default=None,
|
||||
help=(
|
||||
"Attribute aider commits in the git committer name (default: True). If explicitly set"
|
||||
" to True, overrides --attribute-co-authored-by precedence for aider edits."
|
||||
),
|
||||
)
|
||||
group.add_argument(
|
||||
"--attribute-commit-message-author",
|
||||
|
@ -448,6 +454,16 @@ def get_parser(default_config_files, git_root):
|
|||
default=False,
|
||||
help="Prefix all commit messages with 'aider: ' (default: False)",
|
||||
)
|
||||
group.add_argument(
|
||||
"--attribute-co-authored-by",
|
||||
action=argparse.BooleanOptionalAction,
|
||||
default=False,
|
||||
help=(
|
||||
"Attribute aider edits using the Co-authored-by trailer in the commit message"
|
||||
" (default: False). If True, this takes precedence over default --attribute-author and"
|
||||
" --attribute-committer behavior unless they are explicitly set to True."
|
||||
),
|
||||
)
|
||||
group.add_argument(
|
||||
"--git-commit-verify",
|
||||
action=argparse.BooleanOptionalAction,
|
||||
|
@ -670,6 +686,12 @@ def get_parser(default_config_files, git_root):
|
|||
|
||||
######
|
||||
group = parser.add_argument_group("Other settings")
|
||||
group.add_argument(
|
||||
"--disable-playwright",
|
||||
action="store_true",
|
||||
help="Never prompt for or attempt to install Playwright for web scraping (default: False).",
|
||||
default=False,
|
||||
)
|
||||
group.add_argument(
|
||||
"--file",
|
||||
action="append",
|
||||
|
|
|
@ -114,8 +114,6 @@ class Coder:
|
|||
partial_response_tool_call = []
|
||||
commit_before_message = []
|
||||
message_cost = 0.0
|
||||
message_tokens_sent = 0
|
||||
message_tokens_received = 0
|
||||
add_cache_headers = False
|
||||
cache_warming_thread = None
|
||||
num_cache_warming_pings = 0
|
||||
|
@ -183,6 +181,8 @@ class Coder:
|
|||
commands=from_coder.commands.clone(),
|
||||
total_cost=from_coder.total_cost,
|
||||
ignore_mentions=from_coder.ignore_mentions,
|
||||
total_tokens_sent=from_coder.total_tokens_sent,
|
||||
total_tokens_received=from_coder.total_tokens_received,
|
||||
file_watcher=from_coder.file_watcher,
|
||||
)
|
||||
use_kwargs.update(update) # override to complete the switch
|
||||
|
@ -335,6 +335,8 @@ class Coder:
|
|||
chat_language=None,
|
||||
detect_urls=True,
|
||||
ignore_mentions=None,
|
||||
total_tokens_sent=0,
|
||||
total_tokens_received=0,
|
||||
file_watcher=None,
|
||||
auto_copy_context=False,
|
||||
auto_accept_architect=True,
|
||||
|
@ -383,6 +385,10 @@ class Coder:
|
|||
self.need_commit_before_edits = set()
|
||||
|
||||
self.total_cost = total_cost
|
||||
self.total_tokens_sent = total_tokens_sent
|
||||
self.total_tokens_received = total_tokens_received
|
||||
self.message_tokens_sent = 0
|
||||
self.message_tokens_received = 0
|
||||
|
||||
self.verbose = verbose
|
||||
self.abs_fnames = set()
|
||||
|
@ -1206,14 +1212,13 @@ class Coder:
|
|||
language=language,
|
||||
)
|
||||
|
||||
if self.main_model.system_prompt_prefix:
|
||||
prompt = self.main_model.system_prompt_prefix + prompt
|
||||
|
||||
return prompt
|
||||
|
||||
def format_chat_chunks(self):
|
||||
self.choose_fence()
|
||||
main_sys = self.fmt_system_prompt(self.gpt_prompts.main_system)
|
||||
if self.main_model.system_prompt_prefix:
|
||||
main_sys = self.main_model.system_prompt_prefix + "\n" + main_sys
|
||||
|
||||
example_messages = []
|
||||
if self.main_model.examples_as_sys_msg:
|
||||
|
@ -2186,6 +2191,44 @@ class Coder:
|
|||
self.usage_report = tokens_report
|
||||
return
|
||||
|
||||
try:
|
||||
# Try and use litellm's built in cost calculator. Seems to work for non-streaming only?
|
||||
cost = litellm.completion_cost(completion_response=completion)
|
||||
except Exception:
|
||||
cost = 0
|
||||
|
||||
if not cost:
|
||||
cost = self.compute_costs_from_tokens(
|
||||
prompt_tokens, completion_tokens, cache_write_tokens, cache_hit_tokens
|
||||
)
|
||||
|
||||
self.total_cost += cost
|
||||
self.message_cost += cost
|
||||
|
||||
def format_cost(value):
|
||||
if value == 0:
|
||||
return "0.00"
|
||||
magnitude = abs(value)
|
||||
if magnitude >= 0.01:
|
||||
return f"{value:.2f}"
|
||||
else:
|
||||
return f"{value:.{max(2, 2 - int(math.log10(magnitude)))}f}"
|
||||
|
||||
cost_report = (
|
||||
f"Cost: ${format_cost(self.message_cost)} message,"
|
||||
f" ${format_cost(self.total_cost)} session."
|
||||
)
|
||||
|
||||
if cache_hit_tokens and cache_write_tokens:
|
||||
sep = "\n"
|
||||
else:
|
||||
sep = " "
|
||||
|
||||
self.usage_report = tokens_report + sep + cost_report
|
||||
|
||||
def compute_costs_from_tokens(
|
||||
self, prompt_tokens, completion_tokens, cache_write_tokens, cache_hit_tokens
|
||||
):
|
||||
cost = 0
|
||||
|
||||
input_cost_per_token = self.main_model.info.get("input_cost_per_token") or 0
|
||||
|
@ -2213,35 +2256,15 @@ class Coder:
|
|||
cost += prompt_tokens * input_cost_per_token
|
||||
|
||||
cost += completion_tokens * output_cost_per_token
|
||||
|
||||
self.total_cost += cost
|
||||
self.message_cost += cost
|
||||
|
||||
def format_cost(value):
|
||||
if value == 0:
|
||||
return "0.00"
|
||||
magnitude = abs(value)
|
||||
if magnitude >= 0.01:
|
||||
return f"{value:.2f}"
|
||||
else:
|
||||
return f"{value:.{max(2, 2 - int(math.log10(magnitude)))}f}"
|
||||
|
||||
cost_report = (
|
||||
f"Cost: ${format_cost(self.message_cost)} message,"
|
||||
f" ${format_cost(self.total_cost)} session."
|
||||
)
|
||||
|
||||
if cache_hit_tokens and cache_write_tokens:
|
||||
sep = "\n"
|
||||
else:
|
||||
sep = " "
|
||||
|
||||
self.usage_report = tokens_report + sep + cost_report
|
||||
return cost
|
||||
|
||||
def show_usage_report(self):
|
||||
if not self.usage_report:
|
||||
return
|
||||
|
||||
self.total_tokens_sent += self.message_tokens_sent
|
||||
self.total_tokens_received += self.message_tokens_received
|
||||
|
||||
self.io.tool_output(self.usage_report)
|
||||
|
||||
prompt_tokens = self.message_tokens_sent
|
||||
|
@ -2516,7 +2539,7 @@ class Coder:
|
|||
context = self.get_context_from_history(self.cur_messages)
|
||||
|
||||
try:
|
||||
res = self.repo.commit(fnames=edited, context=context, aider_edits=True)
|
||||
res = self.repo.commit(fnames=edited, context=context, aider_edits=True, coder=self)
|
||||
if res:
|
||||
self.show_auto_commit_outcome(res)
|
||||
commit_hash, commit_message = res
|
||||
|
@ -2552,7 +2575,7 @@ class Coder:
|
|||
if not self.repo:
|
||||
return
|
||||
|
||||
self.repo.commit(fnames=self.need_commit_before_edits)
|
||||
self.repo.commit(fnames=self.need_commit_before_edits, coder=self)
|
||||
|
||||
# files changed, move cur messages back behind the files messages
|
||||
# self.move_back_cur_messages(self.gpt_prompts.files_content_local_edits)
|
||||
|
|
|
@ -220,12 +220,18 @@ class Commands:
|
|||
|
||||
self.io.tool_output(f"Scraping {url}...")
|
||||
if not self.scraper:
|
||||
res = install_playwright(self.io)
|
||||
if not res:
|
||||
self.io.tool_warning("Unable to initialize playwright.")
|
||||
disable_playwright = getattr(self.args, "disable_playwright", False)
|
||||
if disable_playwright:
|
||||
res = False
|
||||
else:
|
||||
res = install_playwright(self.io)
|
||||
if not res:
|
||||
self.io.tool_warning("Unable to initialize playwright.")
|
||||
|
||||
self.scraper = Scraper(
|
||||
print_error=self.io.tool_error, playwright_available=res, verify_ssl=self.verify_ssl
|
||||
print_error=self.io.tool_error,
|
||||
playwright_available=res,
|
||||
verify_ssl=self.verify_ssl,
|
||||
)
|
||||
|
||||
content = self.scraper.scrape(url) or ""
|
||||
|
|
|
@ -11,7 +11,7 @@ from aider.coders import Coder
|
|||
from aider.dump import dump # noqa: F401
|
||||
from aider.io import InputOutput
|
||||
from aider.main import main as cli_main
|
||||
from aider.scrape import Scraper
|
||||
from aider.scrape import Scraper, has_playwright
|
||||
|
||||
|
||||
class CaptureIO(InputOutput):
|
||||
|
@ -484,7 +484,7 @@ class GUI:
|
|||
url = self.web_content
|
||||
|
||||
if not self.state.scraper:
|
||||
self.scraper = Scraper(print_error=self.info)
|
||||
self.scraper = Scraper(print_error=self.info, playwright_available=has_playwright())
|
||||
|
||||
content = self.scraper.scrape(url) or ""
|
||||
if content.strip():
|
||||
|
|
|
@ -4,7 +4,7 @@ import subprocess
|
|||
import sys
|
||||
import traceback
|
||||
import warnings
|
||||
import shlex
|
||||
import oslex
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
|
@ -45,7 +45,7 @@ class Linter:
|
|||
return fname
|
||||
|
||||
def run_cmd(self, cmd, rel_fname, code):
|
||||
cmd += " " + shlex.quote(rel_fname)
|
||||
cmd += " " + oslex.quote(rel_fname)
|
||||
|
||||
returncode = 0
|
||||
stdout = ""
|
||||
|
|
|
@ -905,6 +905,7 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
|
|||
commit_prompt=args.commit_prompt,
|
||||
subtree_only=args.subtree_only,
|
||||
git_commit_verify=args.git_commit_verify,
|
||||
attribute_co_authored_by=args.attribute_co_authored_by, # Pass the arg
|
||||
)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
|
123
aider/models.py
123
aider/models.py
|
@ -91,8 +91,8 @@ MODEL_ALIASES = {
|
|||
"flash": "gemini/gemini-2.5-flash-preview-04-17",
|
||||
"quasar": "openrouter/openrouter/quasar-alpha",
|
||||
"r1": "deepseek/deepseek-reasoner",
|
||||
"gemini-2.5-pro": "gemini/gemini-2.5-pro-exp-03-25",
|
||||
"gemini": "gemini/gemini-2.5-pro-preview-03-25",
|
||||
"gemini-2.5-pro": "gemini/gemini-2.5-pro-preview-05-06",
|
||||
"gemini": "gemini/gemini-2.5-pro-preview-05-06",
|
||||
"gemini-exp": "gemini/gemini-2.5-pro-exp-03-25",
|
||||
"grok3": "xai/grok-3-beta",
|
||||
"optimus": "openrouter/openrouter/optimus-alpha",
|
||||
|
@ -231,8 +231,62 @@ class ModelInfoManager:
|
|||
if litellm_info:
|
||||
return litellm_info
|
||||
|
||||
if not cached_info and model.startswith("openrouter/"):
|
||||
openrouter_info = self.fetch_openrouter_model_info(model)
|
||||
if openrouter_info:
|
||||
return openrouter_info
|
||||
|
||||
return cached_info
|
||||
|
||||
def fetch_openrouter_model_info(self, model):
|
||||
"""
|
||||
Fetch model info by scraping the openrouter model page.
|
||||
Expected URL: https://openrouter.ai/<model_route>
|
||||
Example: openrouter/qwen/qwen-2.5-72b-instruct:free
|
||||
Returns a dict with keys: max_tokens, max_input_tokens, max_output_tokens,
|
||||
input_cost_per_token, output_cost_per_token.
|
||||
"""
|
||||
url_part = model[len("openrouter/") :]
|
||||
url = "https://openrouter.ai/" + url_part
|
||||
try:
|
||||
import requests
|
||||
|
||||
response = requests.get(url, timeout=5, verify=self.verify_ssl)
|
||||
if response.status_code != 200:
|
||||
return {}
|
||||
html = response.text
|
||||
import re
|
||||
|
||||
if re.search(
|
||||
rf"The model\s*.*{re.escape(url_part)}.* is not available", html, re.IGNORECASE
|
||||
):
|
||||
print(f"\033[91mError: Model '{url_part}' is not available\033[0m")
|
||||
return {}
|
||||
text = re.sub(r"<[^>]+>", " ", html)
|
||||
context_match = re.search(r"([\d,]+)\s*context", text)
|
||||
if context_match:
|
||||
context_str = context_match.group(1).replace(",", "")
|
||||
context_size = int(context_str)
|
||||
else:
|
||||
context_size = None
|
||||
input_cost_match = re.search(r"\$\s*([\d.]+)\s*/M input tokens", text, re.IGNORECASE)
|
||||
output_cost_match = re.search(r"\$\s*([\d.]+)\s*/M output tokens", text, re.IGNORECASE)
|
||||
input_cost = float(input_cost_match.group(1)) / 1000000 if input_cost_match else None
|
||||
output_cost = float(output_cost_match.group(1)) / 1000000 if output_cost_match else None
|
||||
if context_size is None or input_cost is None or output_cost is None:
|
||||
return {}
|
||||
params = {
|
||||
"max_input_tokens": context_size,
|
||||
"max_tokens": context_size,
|
||||
"max_output_tokens": context_size,
|
||||
"input_cost_per_token": input_cost,
|
||||
"output_cost_per_token": output_cost,
|
||||
}
|
||||
return params
|
||||
except Exception as e:
|
||||
print("Error fetching openrouter info:", str(e))
|
||||
return {}
|
||||
|
||||
|
||||
model_info_manager = ModelInfoManager()
|
||||
|
||||
|
@ -332,6 +386,15 @@ class Model(ModelSettings):
|
|||
# For non-dict values, simply update
|
||||
self.extra_params[key] = value
|
||||
|
||||
# Ensure OpenRouter models accept thinking_tokens and reasoning_effort
|
||||
if self.name.startswith("openrouter/"):
|
||||
if self.accepts_settings is None:
|
||||
self.accepts_settings = []
|
||||
if "thinking_tokens" not in self.accepts_settings:
|
||||
self.accepts_settings.append("thinking_tokens")
|
||||
if "reasoning_effort" not in self.accepts_settings:
|
||||
self.accepts_settings.append("reasoning_effort")
|
||||
|
||||
def apply_generic_model_settings(self, model):
|
||||
if "/o3-mini" in model:
|
||||
self.edit_format = "diff"
|
||||
|
@ -460,6 +523,11 @@ class Model(ModelSettings):
|
|||
self.extra_params = dict(top_p=0.95)
|
||||
return # <--
|
||||
|
||||
if "qwen3" in model and "235b" in model:
|
||||
self.edit_format = "diff"
|
||||
self.use_repo_map = True
|
||||
return # <--
|
||||
|
||||
# use the defaults
|
||||
if self.edit_format == "diff":
|
||||
self.use_repo_map = True
|
||||
|
@ -659,11 +727,18 @@ class Model(ModelSettings):
|
|||
def set_reasoning_effort(self, effort):
|
||||
"""Set the reasoning effort parameter for models that support it"""
|
||||
if effort is not None:
|
||||
if not self.extra_params:
|
||||
self.extra_params = {}
|
||||
if "extra_body" not in self.extra_params:
|
||||
self.extra_params["extra_body"] = {}
|
||||
self.extra_params["extra_body"]["reasoning_effort"] = effort
|
||||
if self.name.startswith("openrouter/"):
|
||||
if not self.extra_params:
|
||||
self.extra_params = {}
|
||||
if "extra_body" not in self.extra_params:
|
||||
self.extra_params["extra_body"] = {}
|
||||
self.extra_params["extra_body"]["reasoning"] = {"effort": effort}
|
||||
else:
|
||||
if not self.extra_params:
|
||||
self.extra_params = {}
|
||||
if "extra_body" not in self.extra_params:
|
||||
self.extra_params["extra_body"] = {}
|
||||
self.extra_params["extra_body"]["reasoning_effort"] = effort
|
||||
|
||||
def parse_token_value(self, value):
|
||||
"""
|
||||
|
@ -709,7 +784,9 @@ class Model(ModelSettings):
|
|||
|
||||
# OpenRouter models use 'reasoning' instead of 'thinking'
|
||||
if self.name.startswith("openrouter/"):
|
||||
self.extra_params["reasoning"] = {"max_tokens": num_tokens}
|
||||
if "extra_body" not in self.extra_params:
|
||||
self.extra_params["extra_body"] = {}
|
||||
self.extra_params["extra_body"]["reasoning"] = {"max_tokens": num_tokens}
|
||||
else:
|
||||
self.extra_params["thinking"] = {"type": "enabled", "budget_tokens": num_tokens}
|
||||
|
||||
|
@ -719,8 +796,13 @@ class Model(ModelSettings):
|
|||
|
||||
if self.extra_params:
|
||||
# Check for OpenRouter reasoning format
|
||||
if "reasoning" in self.extra_params and "max_tokens" in self.extra_params["reasoning"]:
|
||||
budget = self.extra_params["reasoning"]["max_tokens"]
|
||||
if self.name.startswith("openrouter/"):
|
||||
if (
|
||||
"extra_body" in self.extra_params
|
||||
and "reasoning" in self.extra_params["extra_body"]
|
||||
and "max_tokens" in self.extra_params["extra_body"]["reasoning"]
|
||||
):
|
||||
budget = self.extra_params["extra_body"]["reasoning"]["max_tokens"]
|
||||
# Check for standard thinking format
|
||||
elif (
|
||||
"thinking" in self.extra_params and "budget_tokens" in self.extra_params["thinking"]
|
||||
|
@ -750,12 +832,21 @@ class Model(ModelSettings):
|
|||
|
||||
def get_reasoning_effort(self):
|
||||
"""Get reasoning effort value if available"""
|
||||
if (
|
||||
self.extra_params
|
||||
and "extra_body" in self.extra_params
|
||||
and "reasoning_effort" in self.extra_params["extra_body"]
|
||||
):
|
||||
return self.extra_params["extra_body"]["reasoning_effort"]
|
||||
if self.extra_params:
|
||||
# Check for OpenRouter reasoning format
|
||||
if self.name.startswith("openrouter/"):
|
||||
if (
|
||||
"extra_body" in self.extra_params
|
||||
and "reasoning" in self.extra_params["extra_body"]
|
||||
and "effort" in self.extra_params["extra_body"]["reasoning"]
|
||||
):
|
||||
return self.extra_params["extra_body"]["reasoning"]["effort"]
|
||||
# Check for standard reasoning_effort format (e.g. in extra_body)
|
||||
elif (
|
||||
"extra_body" in self.extra_params
|
||||
and "reasoning_effort" in self.extra_params["extra_body"]
|
||||
):
|
||||
return self.extra_params["extra_body"]["reasoning_effort"]
|
||||
return None
|
||||
|
||||
def is_deepseek_r1(self):
|
||||
|
|
115
aider/queries/tree-sitter-language-pack/ocaml-tags.scm
Normal file
115
aider/queries/tree-sitter-language-pack/ocaml-tags.scm
Normal file
|
@ -0,0 +1,115 @@
|
|||
; Modules
|
||||
;--------
|
||||
|
||||
(
|
||||
(comment)? @doc .
|
||||
(module_definition (module_binding (module_name) @name.definition.module) @definition.module)
|
||||
(#strip! @doc "^\\(\\*\\*?\\s*|\\s\\*\\)$")
|
||||
)
|
||||
|
||||
(module_path (module_name) @name.reference.module) @reference.module
|
||||
|
||||
; Module types
|
||||
;--------------
|
||||
|
||||
(
|
||||
(comment)? @doc .
|
||||
(module_type_definition (module_type_name) @name.definition.interface) @definition.interface
|
||||
(#strip! @doc "^\\(\\*\\*?\\s*|\\s\\*\\)$")
|
||||
)
|
||||
|
||||
(module_type_path (module_type_name) @name.reference.implementation) @reference.implementation
|
||||
|
||||
; Functions
|
||||
;----------
|
||||
|
||||
(
|
||||
(comment)? @doc .
|
||||
(value_definition
|
||||
[
|
||||
(let_binding
|
||||
pattern: (value_name) @name.definition.function
|
||||
(parameter))
|
||||
(let_binding
|
||||
pattern: (value_name) @name.definition.function
|
||||
body: [(fun_expression) (function_expression)])
|
||||
] @definition.function
|
||||
)
|
||||
(#strip! @doc "^\\(\\*\\*?\\s*|\\s\\*\\)$")
|
||||
)
|
||||
|
||||
(
|
||||
(comment)? @doc .
|
||||
(external (value_name) @name.definition.function) @definition.function
|
||||
(#strip! @doc "^\\(\\*\\*?\\s*|\\s\\*\\)$")
|
||||
)
|
||||
|
||||
(application_expression
|
||||
function: (value_path (value_name) @name.reference.call)) @reference.call
|
||||
|
||||
(infix_expression
|
||||
left: (value_path (value_name) @name.reference.call)
|
||||
operator: (concat_operator) @reference.call
|
||||
(#eq? @reference.call "@@"))
|
||||
|
||||
(infix_expression
|
||||
operator: (rel_operator) @reference.call
|
||||
right: (value_path (value_name) @name.reference.call)
|
||||
(#eq? @reference.call "|>"))
|
||||
|
||||
; Operator
|
||||
;---------
|
||||
|
||||
(
|
||||
(comment)? @doc .
|
||||
(value_definition
|
||||
(let_binding
|
||||
pattern: (parenthesized_operator (_) @name.definition.function)) @definition.function)
|
||||
(#strip! @doc "^\\(\\*\\*?\\s*|\\s\\*\\)$")
|
||||
)
|
||||
|
||||
[
|
||||
(prefix_operator)
|
||||
(sign_operator)
|
||||
(pow_operator)
|
||||
(mult_operator)
|
||||
(add_operator)
|
||||
(concat_operator)
|
||||
(rel_operator)
|
||||
(and_operator)
|
||||
(or_operator)
|
||||
(assign_operator)
|
||||
(hash_operator)
|
||||
(indexing_operator)
|
||||
(let_operator)
|
||||
(let_and_operator)
|
||||
(match_operator)
|
||||
] @name.reference.call @reference.call
|
||||
|
||||
; Classes
|
||||
;--------
|
||||
|
||||
(
|
||||
(comment)? @doc .
|
||||
[
|
||||
(class_definition (class_binding (class_name) @name.definition.class) @definition.class)
|
||||
(class_type_definition (class_type_binding (class_type_name) @name.definition.class) @definition.class)
|
||||
]
|
||||
(#strip! @doc "^\\(\\*\\*?\\s*|\\s\\*\\)$")
|
||||
)
|
||||
|
||||
[
|
||||
(class_path (class_name) @name.reference.class)
|
||||
(class_type_path (class_type_name) @name.reference.class)
|
||||
] @reference.class
|
||||
|
||||
; Methods
|
||||
;--------
|
||||
|
||||
(
|
||||
(comment)? @doc .
|
||||
(method_definition (method_name) @name.definition.method) @definition.method
|
||||
(#strip! @doc "^\\(\\*\\*?\\s*|\\s\\*\\)$")
|
||||
)
|
||||
|
||||
(method_invocation (method_name) @name.reference.call) @reference.call
|
|
@ -0,0 +1,98 @@
|
|||
; Modules
|
||||
;--------
|
||||
|
||||
(
|
||||
(comment)? @doc .
|
||||
(module_definition
|
||||
(module_binding (module_name) @name) @definition.module
|
||||
)
|
||||
(#strip! @doc "^\\(\\*+\\s*|\\s*\\*+\\)$")
|
||||
)
|
||||
|
||||
(module_path (module_name) @name) @reference.module
|
||||
(extended_module_path (module_name) @name) @reference.module
|
||||
|
||||
(
|
||||
(comment)? @doc .
|
||||
(module_type_definition (module_type_name) @name) @definition.interface
|
||||
(#strip! @doc "^\\(\\*+\\s*|\\s*\\*+\\)$")
|
||||
)
|
||||
|
||||
(module_type_path (module_type_name) @name) @reference.implementation
|
||||
|
||||
|
||||
; Classes
|
||||
;--------
|
||||
|
||||
(
|
||||
(comment)? @doc .
|
||||
[
|
||||
(class_definition
|
||||
(class_binding (class_name) @name) @definition.class
|
||||
)
|
||||
(class_type_definition
|
||||
(class_type_binding (class_type_name) @name) @definition.class
|
||||
)
|
||||
]
|
||||
(#strip! @doc "^\\(\\*+\\s*|\\s*\\*+\\)$")
|
||||
)
|
||||
|
||||
[
|
||||
(class_path (class_name) @name)
|
||||
(class_type_path (class_type_name) @name)
|
||||
] @reference.class
|
||||
|
||||
(
|
||||
(comment)? @doc .
|
||||
(method_definition (method_name) @name) @definition.method
|
||||
(#strip! @doc "^\\(\\*+\\s*|\\s*\\*+\\)$")
|
||||
)
|
||||
|
||||
(method_invocation (method_name) @name) @reference.call
|
||||
|
||||
|
||||
; Types
|
||||
;------
|
||||
|
||||
(
|
||||
(comment)? @doc .
|
||||
(type_definition
|
||||
(type_binding
|
||||
name: [
|
||||
(type_constructor) @name
|
||||
(type_constructor_path (type_constructor) @name)
|
||||
]
|
||||
) @definition.type
|
||||
)
|
||||
(#strip! @doc "^\\(\\*+\\s*|\\s*\\*+\\)$")
|
||||
)
|
||||
|
||||
(type_constructor_path (type_constructor) @name) @reference.type
|
||||
|
||||
[
|
||||
(constructor_declaration (constructor_name) @name)
|
||||
(tag_specification (tag) @name)
|
||||
] @definition.enum_variant
|
||||
|
||||
[
|
||||
(constructor_path (constructor_name) @name)
|
||||
(tag) @name
|
||||
] @reference.enum_variant
|
||||
|
||||
(field_declaration (field_name) @name) @definition.field
|
||||
|
||||
(field_path (field_name) @name) @reference.field
|
||||
|
||||
(
|
||||
(comment)? @doc .
|
||||
(external (value_name) @name) @definition.function
|
||||
(#strip! @doc "^\\(\\*+\\s*|\\s*\\*+\\)$")
|
||||
)
|
||||
|
||||
(
|
||||
(comment)? @doc .
|
||||
(value_specification
|
||||
(value_name) @name.definition.function
|
||||
) @definition.function
|
||||
(#strip! @doc "^\\(\\*+\\s*|\\s*\\*+\\)$")
|
||||
)
|
98
aider/queries/tree-sitter-languages/ocaml_interface-tags.scm
Normal file
98
aider/queries/tree-sitter-languages/ocaml_interface-tags.scm
Normal file
|
@ -0,0 +1,98 @@
|
|||
; Modules
|
||||
;--------
|
||||
|
||||
(
|
||||
(comment)? @doc .
|
||||
(module_definition
|
||||
(module_binding (module_name) @name) @definition.module
|
||||
)
|
||||
(#strip! @doc "^\\(\\*+\\s*|\\s*\\*+\\)$")
|
||||
)
|
||||
|
||||
(module_path (module_name) @name) @reference.module
|
||||
(extended_module_path (module_name) @name) @reference.module
|
||||
|
||||
(
|
||||
(comment)? @doc .
|
||||
(module_type_definition (module_type_name) @name) @definition.interface
|
||||
(#strip! @doc "^\\(\\*+\\s*|\\s*\\*+\\)$")
|
||||
)
|
||||
|
||||
(module_type_path (module_type_name) @name) @reference.implementation
|
||||
|
||||
|
||||
; Classes
|
||||
;--------
|
||||
|
||||
(
|
||||
(comment)? @doc .
|
||||
[
|
||||
(class_definition
|
||||
(class_binding (class_name) @name) @definition.class
|
||||
)
|
||||
(class_type_definition
|
||||
(class_type_binding (class_type_name) @name) @definition.class
|
||||
)
|
||||
]
|
||||
(#strip! @doc "^\\(\\*+\\s*|\\s*\\*+\\)$")
|
||||
)
|
||||
|
||||
[
|
||||
(class_path (class_name) @name)
|
||||
(class_type_path (class_type_name) @name)
|
||||
] @reference.class
|
||||
|
||||
(
|
||||
(comment)? @doc .
|
||||
(method_definition (method_name) @name) @definition.method
|
||||
(#strip! @doc "^\\(\\*+\\s*|\\s*\\*+\\)$")
|
||||
)
|
||||
|
||||
(method_invocation (method_name) @name) @reference.call
|
||||
|
||||
|
||||
; Types
|
||||
;------
|
||||
|
||||
(
|
||||
(comment)? @doc .
|
||||
(type_definition
|
||||
(type_binding
|
||||
name: [
|
||||
(type_constructor) @name
|
||||
(type_constructor_path (type_constructor) @name)
|
||||
]
|
||||
) @definition.type
|
||||
)
|
||||
(#strip! @doc "^\\(\\*+\\s*|\\s*\\*+\\)$")
|
||||
)
|
||||
|
||||
(type_constructor_path (type_constructor) @name) @reference.type
|
||||
|
||||
[
|
||||
(constructor_declaration (constructor_name) @name)
|
||||
(tag_specification (tag) @name)
|
||||
] @definition.enum_variant
|
||||
|
||||
[
|
||||
(constructor_path (constructor_name) @name)
|
||||
(tag) @name
|
||||
] @reference.enum_variant
|
||||
|
||||
(field_declaration (field_name) @name) @definition.field
|
||||
|
||||
(field_path (field_name) @name) @reference.field
|
||||
|
||||
(
|
||||
(comment)? @doc .
|
||||
(external (value_name) @name) @definition.function
|
||||
(#strip! @doc "^\\(\\*+\\s*|\\s*\\*+\\)$")
|
||||
)
|
||||
|
||||
(
|
||||
(comment)? @doc .
|
||||
(value_specification
|
||||
(value_name) @name.definition.function
|
||||
) @definition.function
|
||||
(#strip! @doc "^\\(\\*+\\s*|\\s*\\*+\\)$")
|
||||
)
|
191
aider/repo.py
191
aider/repo.py
|
@ -1,3 +1,4 @@
|
|||
import contextlib
|
||||
import os
|
||||
import time
|
||||
from pathlib import Path, PurePosixPath
|
||||
|
@ -34,6 +35,19 @@ ANY_GIT_ERROR += [
|
|||
ANY_GIT_ERROR = tuple(ANY_GIT_ERROR)
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def set_git_env(var_name, value, original_value):
|
||||
"""Temporarily set a Git environment variable."""
|
||||
os.environ[var_name] = value
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
if original_value is not None:
|
||||
os.environ[var_name] = original_value
|
||||
elif var_name in os.environ:
|
||||
del os.environ[var_name]
|
||||
|
||||
|
||||
class GitRepo:
|
||||
repo = None
|
||||
aider_ignore_file = None
|
||||
|
@ -58,6 +72,7 @@ class GitRepo:
|
|||
commit_prompt=None,
|
||||
subtree_only=False,
|
||||
git_commit_verify=True,
|
||||
attribute_co_authored_by=False, # Added parameter
|
||||
):
|
||||
self.io = io
|
||||
self.models = models
|
||||
|
@ -69,6 +84,7 @@ class GitRepo:
|
|||
self.attribute_committer = attribute_committer
|
||||
self.attribute_commit_message_author = attribute_commit_message_author
|
||||
self.attribute_commit_message_committer = attribute_commit_message_committer
|
||||
self.attribute_co_authored_by = attribute_co_authored_by # Assign from parameter
|
||||
self.commit_prompt = commit_prompt
|
||||
self.subtree_only = subtree_only
|
||||
self.git_commit_verify = git_commit_verify
|
||||
|
@ -111,7 +127,71 @@ class GitRepo:
|
|||
if aider_ignore_file:
|
||||
self.aider_ignore_file = Path(aider_ignore_file)
|
||||
|
||||
def commit(self, fnames=None, context=None, message=None, aider_edits=False):
|
||||
def commit(self, fnames=None, context=None, message=None, aider_edits=False, coder=None):
|
||||
"""
|
||||
Commit the specified files or all dirty files if none are specified.
|
||||
|
||||
Args:
|
||||
fnames (list, optional): List of filenames to commit. Defaults to None (commit all
|
||||
dirty files).
|
||||
context (str, optional): Context for generating the commit message. Defaults to None.
|
||||
message (str, optional): Explicit commit message. Defaults to None (generate message).
|
||||
aider_edits (bool, optional): Whether the changes were made by Aider. Defaults to False.
|
||||
This affects attribution logic.
|
||||
coder (Coder, optional): The Coder instance, used to access config and model info.
|
||||
Defaults to None.
|
||||
|
||||
Returns:
|
||||
tuple(str, str) or None: The commit hash and commit message if successful, else None.
|
||||
|
||||
Attribution Logic:
|
||||
------------------
|
||||
This method handles Git commit attribution based on configuration flags and whether
|
||||
Aider generated the changes (`aider_edits`).
|
||||
|
||||
Key Concepts:
|
||||
- Author: The person who originally wrote the code changes.
|
||||
- Committer: The person who last applied the commit to the repository.
|
||||
- aider_edits=True: Changes were generated by Aider (LLM).
|
||||
- aider_edits=False: Commit is user-driven (e.g., /commit manually staged changes).
|
||||
- Explicit Setting: A flag (--attribute-...) is set to True or False via command line
|
||||
or config file.
|
||||
- Implicit Default: A flag is not explicitly set, defaulting to None in args, which is
|
||||
interpreted as True unless overridden by other logic.
|
||||
|
||||
Flags:
|
||||
- --attribute-author: Modify Author name to "User Name (aider)".
|
||||
- --attribute-committer: Modify Committer name to "User Name (aider)".
|
||||
- --attribute-co-authored-by: Add "Co-authored-by: aider (<model>) <noreply@aider.chat>"
|
||||
trailer to the commit message.
|
||||
|
||||
Behavior Summary:
|
||||
|
||||
1. When aider_edits = True (AI Changes):
|
||||
- If --attribute-co-authored-by=True:
|
||||
- Co-authored-by trailer IS ADDED.
|
||||
- Author/Committer names are NOT modified by default (co-authored-by takes precedence).
|
||||
- EXCEPTION: If --attribute-author/--attribute-committer is EXPLICITLY True,
|
||||
the respective name IS modified (explicit overrides precedence).
|
||||
- If --attribute-co-authored-by=False:
|
||||
- Co-authored-by trailer is NOT added.
|
||||
- Author/Committer names ARE modified by default (implicit True).
|
||||
- EXCEPTION: If --attribute-author/--attribute-committer is EXPLICITLY False,
|
||||
the respective name is NOT modified.
|
||||
|
||||
2. When aider_edits = False (User Changes):
|
||||
- --attribute-co-authored-by is IGNORED (trailer never added).
|
||||
- Author name is NEVER modified (--attribute-author ignored).
|
||||
- Committer name IS modified by default (implicit True, as Aider runs `git commit`).
|
||||
- EXCEPTION: If --attribute-committer is EXPLICITLY False, the name is NOT modified.
|
||||
|
||||
Resulting Scenarios:
|
||||
- Standard AI edit (defaults): Co-authored-by=False -> Author=You(aider), Committer=You(aider)
|
||||
- AI edit with Co-authored-by (default): Co-authored-by=True -> Author=You, Committer=You, Trailer added
|
||||
- AI edit with Co-authored-by + Explicit Author: Co-authored-by=True, --attribute-author -> Author=You(aider), Committer=You, Trailer added
|
||||
- User commit (defaults): aider_edits=False -> Author=You, Committer=You(aider)
|
||||
- User commit with explicit no-committer: aider_edits=False, --no-attribute-committer -> Author=You, Committer=You
|
||||
"""
|
||||
if not fnames and not self.repo.is_dirty():
|
||||
return
|
||||
|
||||
|
@ -124,17 +204,68 @@ class GitRepo:
|
|||
else:
|
||||
commit_message = self.get_commit_message(diffs, context)
|
||||
|
||||
if aider_edits and self.attribute_commit_message_author:
|
||||
commit_message = "aider: " + commit_message
|
||||
elif self.attribute_commit_message_committer:
|
||||
commit_message = "aider: " + commit_message
|
||||
# Retrieve attribute settings, prioritizing coder.args if available
|
||||
if coder and hasattr(coder, "args"):
|
||||
attribute_author = coder.args.attribute_author
|
||||
attribute_committer = coder.args.attribute_committer
|
||||
attribute_commit_message_author = coder.args.attribute_commit_message_author
|
||||
attribute_commit_message_committer = coder.args.attribute_commit_message_committer
|
||||
attribute_co_authored_by = coder.args.attribute_co_authored_by
|
||||
else:
|
||||
# Fallback to self attributes (initialized from config/defaults)
|
||||
attribute_author = self.attribute_author
|
||||
attribute_committer = self.attribute_committer
|
||||
attribute_commit_message_author = self.attribute_commit_message_author
|
||||
attribute_commit_message_committer = self.attribute_commit_message_committer
|
||||
attribute_co_authored_by = self.attribute_co_authored_by
|
||||
|
||||
# Determine explicit settings (None means use default behavior)
|
||||
author_explicit = attribute_author is not None
|
||||
committer_explicit = attribute_committer is not None
|
||||
|
||||
# Determine effective settings (apply default True if not explicit)
|
||||
effective_author = True if attribute_author is None else attribute_author
|
||||
effective_committer = True if attribute_committer is None else attribute_committer
|
||||
|
||||
|
||||
# Determine commit message prefixing
|
||||
prefix_commit_message = aider_edits and (
|
||||
attribute_commit_message_author or attribute_commit_message_committer
|
||||
)
|
||||
|
||||
# Determine Co-authored-by trailer
|
||||
commit_message_trailer = ""
|
||||
if aider_edits and attribute_co_authored_by:
|
||||
model_name = "unknown-model"
|
||||
if coder and hasattr(coder, "main_model") and coder.main_model.name:
|
||||
model_name = coder.main_model.name
|
||||
commit_message_trailer = (
|
||||
f"\n\nCo-authored-by: aider ({model_name}) <noreply@aider.chat>"
|
||||
)
|
||||
|
||||
# Determine if author/committer names should be modified
|
||||
# Author modification applies only to aider edits.
|
||||
# It's used if effective_author is True AND (co-authored-by is False OR author was explicitly set).
|
||||
use_attribute_author = (
|
||||
aider_edits
|
||||
and effective_author
|
||||
and (not attribute_co_authored_by or author_explicit)
|
||||
)
|
||||
|
||||
# Committer modification applies regardless of aider_edits (based on tests).
|
||||
# It's used if effective_committer is True AND (it's not an aider edit with co-authored-by OR committer was explicitly set).
|
||||
use_attribute_committer = effective_committer and (
|
||||
not (aider_edits and attribute_co_authored_by) or committer_explicit
|
||||
)
|
||||
|
||||
|
||||
if not commit_message:
|
||||
commit_message = "(no commit message provided)"
|
||||
|
||||
full_commit_message = commit_message
|
||||
# if context:
|
||||
# full_commit_message += "\n\n# Aider chat conversation:\n\n" + context
|
||||
if prefix_commit_message:
|
||||
commit_message = "aider: " + commit_message
|
||||
|
||||
full_commit_message = commit_message + commit_message_trailer
|
||||
|
||||
cmd = ["-m", full_commit_message]
|
||||
if not self.git_commit_verify:
|
||||
|
@ -152,36 +283,30 @@ class GitRepo:
|
|||
|
||||
original_user_name = self.repo.git.config("--get", "user.name")
|
||||
original_committer_name_env = os.environ.get("GIT_COMMITTER_NAME")
|
||||
original_author_name_env = os.environ.get("GIT_AUTHOR_NAME")
|
||||
committer_name = f"{original_user_name} (aider)"
|
||||
|
||||
if self.attribute_committer:
|
||||
os.environ["GIT_COMMITTER_NAME"] = committer_name
|
||||
|
||||
if aider_edits and self.attribute_author:
|
||||
original_author_name_env = os.environ.get("GIT_AUTHOR_NAME")
|
||||
os.environ["GIT_AUTHOR_NAME"] = committer_name
|
||||
|
||||
try:
|
||||
self.repo.git.commit(cmd)
|
||||
commit_hash = self.get_head_commit_sha(short=True)
|
||||
self.io.tool_output(f"Commit {commit_hash} {commit_message}", bold=True)
|
||||
return commit_hash, commit_message
|
||||
# Use context managers to handle environment variables
|
||||
with contextlib.ExitStack() as stack:
|
||||
if use_attribute_committer:
|
||||
stack.enter_context(
|
||||
set_git_env("GIT_COMMITTER_NAME", committer_name, original_committer_name_env)
|
||||
)
|
||||
if use_attribute_author:
|
||||
stack.enter_context(
|
||||
set_git_env("GIT_AUTHOR_NAME", committer_name, original_author_name_env)
|
||||
)
|
||||
|
||||
# Perform the commit
|
||||
self.repo.git.commit(cmd)
|
||||
commit_hash = self.get_head_commit_sha(short=True)
|
||||
self.io.tool_output(f"Commit {commit_hash} {commit_message}", bold=True)
|
||||
return commit_hash, commit_message
|
||||
|
||||
except ANY_GIT_ERROR as err:
|
||||
self.io.tool_error(f"Unable to commit: {err}")
|
||||
finally:
|
||||
# Restore the env
|
||||
|
||||
if self.attribute_committer:
|
||||
if original_committer_name_env is not None:
|
||||
os.environ["GIT_COMMITTER_NAME"] = original_committer_name_env
|
||||
else:
|
||||
del os.environ["GIT_COMMITTER_NAME"]
|
||||
|
||||
if aider_edits and self.attribute_author:
|
||||
if original_author_name_env is not None:
|
||||
os.environ["GIT_AUTHOR_NAME"] = original_author_name_env
|
||||
else:
|
||||
del os.environ["GIT_AUTHOR_NAME"]
|
||||
# No return here, implicitly returns None
|
||||
|
||||
def get_rel_repo_dir(self):
|
||||
try:
|
||||
|
|
|
@ -403,4 +403,62 @@
|
|||
"supports_audio_output": true,
|
||||
"supports_tool_choice": true
|
||||
},
|
||||
"gemini-2.5-pro-preview-05-06": {
|
||||
"max_tokens": 65536,
|
||||
"max_input_tokens": 1048576,
|
||||
"max_output_tokens": 65536,
|
||||
"max_images_per_prompt": 3000,
|
||||
"max_videos_per_prompt": 10,
|
||||
"max_video_length": 1,
|
||||
"max_audio_length_hours": 8.4,
|
||||
"max_audio_per_prompt": 1,
|
||||
"max_pdf_size_mb": 30,
|
||||
"input_cost_per_audio_token": 0.00000125,
|
||||
"input_cost_per_token": 0.00000125,
|
||||
"input_cost_per_token_above_200k_tokens": 0.0000025,
|
||||
"output_cost_per_token": 0.00001,
|
||||
"output_cost_per_token_above_200k_tokens": 0.000015,
|
||||
"litellm_provider": "vertex_ai-language-models",
|
||||
"mode": "chat",
|
||||
"supports_reasoning": true,
|
||||
"supports_system_messages": true,
|
||||
"supports_function_calling": true,
|
||||
"supports_vision": true,
|
||||
"supports_response_schema": true,
|
||||
"supports_audio_output": false,
|
||||
"supports_tool_choice": true,
|
||||
"supported_endpoints": ["/v1/chat/completions", "/v1/completions", "/v1/batch"],
|
||||
"supported_modalities": ["text", "image", "audio", "video"],
|
||||
"supported_output_modalities": ["text"],
|
||||
"source": "https://ai.google.dev/gemini-api/docs/models#gemini-2.5-flash-preview"
|
||||
},
|
||||
"gemini/gemini-2.5-pro-preview-05-06": {
|
||||
"max_tokens": 65536,
|
||||
"max_input_tokens": 1048576,
|
||||
"max_output_tokens": 65536,
|
||||
"max_images_per_prompt": 3000,
|
||||
"max_videos_per_prompt": 10,
|
||||
"max_video_length": 1,
|
||||
"max_audio_length_hours": 8.4,
|
||||
"max_audio_per_prompt": 1,
|
||||
"max_pdf_size_mb": 30,
|
||||
"input_cost_per_audio_token": 0.0000007,
|
||||
"input_cost_per_token": 0.00000125,
|
||||
"input_cost_per_token_above_200k_tokens": 0.0000025,
|
||||
"output_cost_per_token": 0.00001,
|
||||
"output_cost_per_token_above_200k_tokens": 0.000015,
|
||||
"litellm_provider": "gemini",
|
||||
"mode": "chat",
|
||||
"rpm": 10000,
|
||||
"tpm": 10000000,
|
||||
"supports_system_messages": true,
|
||||
"supports_function_calling": true,
|
||||
"supports_vision": true,
|
||||
"supports_response_schema": true,
|
||||
"supports_audio_output": false,
|
||||
"supports_tool_choice": true,
|
||||
"supported_modalities": ["text", "image", "audio", "video"],
|
||||
"supported_output_modalities": ["text"],
|
||||
"source": "https://ai.google.dev/gemini-api/docs/pricing#gemini-2.5-pro-preview"
|
||||
},
|
||||
}
|
||||
|
|
|
@ -1375,14 +1375,40 @@
|
|||
- name: gemini/gemini-2.5-flash-preview-04-17
|
||||
edit_format: diff
|
||||
use_repo_map: true
|
||||
accepts_settings: ["thinking_tokens"]
|
||||
accepts_settings: ["reasoning_effort", "thinking_tokens"]
|
||||
|
||||
- name: gemini-2.5-flash-preview-04-17
|
||||
edit_format: diff
|
||||
use_repo_map: true
|
||||
accepts_settings: ["thinking_tokens"]
|
||||
accepts_settings: ["reasoning_effort", "thinking_tokens"]
|
||||
|
||||
- name: vertex_ai-language-models/gemini-2.5-flash-preview-04-17
|
||||
edit_format: diff
|
||||
use_repo_map: true
|
||||
accepts_settings: ["thinking_tokens"]
|
||||
accepts_settings: ["reasoning_effort", "thinking_tokens"]
|
||||
|
||||
- name: openrouter/google/gemini-2.5-pro-preview-03-25
|
||||
overeager: true
|
||||
edit_format: diff-fenced
|
||||
use_repo_map: true
|
||||
weak_model_name: openrouter/google/gemini-2.0-flash-001
|
||||
|
||||
- name: gemini/gemini-2.5-pro-preview-05-06
|
||||
overeager: true
|
||||
edit_format: diff-fenced
|
||||
use_repo_map: true
|
||||
weak_model_name: gemini/gemini-2.5-flash-preview-04-17
|
||||
|
||||
- name: vertex_ai/gemini-2.5-pro-preview-05-06
|
||||
edit_format: diff-fenced
|
||||
use_repo_map: true
|
||||
weak_model_name: vertex_ai-language-models/gemini-2.5-flash-preview-04-17
|
||||
overeager: true
|
||||
editor_model_name: vertex_ai-language-models/gemini-2.5-flash-preview-04-17
|
||||
|
||||
- name: openrouter/google/gemini-2.5-pro-preview-05-06
|
||||
overeager: true
|
||||
edit_format: diff-fenced
|
||||
use_repo_map: true
|
||||
weak_model_name: openrouter/google/gemini-2.0-flash-001
|
||||
|
|
@ -14,7 +14,7 @@ aider_user_agent = f"Aider/{__version__} +{urls.website}"
|
|||
# platforms.
|
||||
|
||||
|
||||
def install_playwright(io):
|
||||
def check_env():
|
||||
try:
|
||||
from playwright.sync_api import sync_playwright
|
||||
|
||||
|
@ -29,6 +29,16 @@ def install_playwright(io):
|
|||
except Exception:
|
||||
has_chromium = False
|
||||
|
||||
return has_pip, has_chromium
|
||||
|
||||
|
||||
def has_playwright():
|
||||
has_pip, has_chromium = check_env()
|
||||
return has_pip and has_chromium
|
||||
|
||||
|
||||
def install_playwright(io):
|
||||
has_pip, has_chromium = check_env()
|
||||
if has_pip and has_chromium:
|
||||
return True
|
||||
|
||||
|
@ -262,7 +272,7 @@ def slimdown_html(soup):
|
|||
|
||||
|
||||
def main(url):
|
||||
scraper = Scraper()
|
||||
scraper = Scraper(playwright_available=has_playwright())
|
||||
content = scraper.scrape(url)
|
||||
print(content)
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import itertools
|
||||
import os
|
||||
import platform
|
||||
import shlex
|
||||
import oslex
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
@ -384,10 +384,7 @@ def printable_shell_command(cmd_list):
|
|||
Returns:
|
||||
str: Shell-escaped command string.
|
||||
"""
|
||||
if platform.system() == "Windows":
|
||||
return subprocess.list2cmdline(cmd_list)
|
||||
else:
|
||||
return shlex.join(cmd_list)
|
||||
return oslex.join(cmd_list)
|
||||
|
||||
|
||||
def main():
|
||||
|
|
|
@ -26,6 +26,28 @@ cog.out(text)
|
|||
|
||||
### main branch
|
||||
|
||||
- Added support for `qwen3-235b` models, including `openrouter/qwen/qwen3-235b-a22b`.
|
||||
- Added support for `gemini-2.5-pro-preview-05-06` models.
|
||||
- Added repomap support for OCaml and OCaml interface files, by Andrey Popp.
|
||||
- Introduced `--attribute-co-authored-by` option to add co-author trailer to commit messages, by Andrew Grigorev.
|
||||
- Updated Gemini model aliases (e.g., `gemini`, `gemini-2.5-pro`) to point to the `05-06` preview versions.
|
||||
- Marked Gemini 2.5 Pro preview models as `overeager` by default.
|
||||
- Updated the default weak model for Gemini 2.5 Pro models to `gemini/gemini-2.5-flash-preview-04-17`.
|
||||
- Corrected `gemini-2.5-pro-exp-03-25` model settings to reflect its lack of support for `thinking_budget`.
|
||||
- Ensured model-specific system prompt prefixes are placed on a new line before the main system prompt.
|
||||
- Added tracking of total tokens sent and received, now included in benchmark statistics.
|
||||
- Automatically fetch model parameters (context window, pricing) for OpenRouter models directly from their website, by Stefan Hladnik.
|
||||
- Enabled support for `thinking_tokens` and `reasoning_effort` parameters for OpenRouter models.
|
||||
- Improved cost calculation using `litellm.completion_cost` where available.
|
||||
- Added model settings for `openrouter/google/gemini-2.5-pro-preview-03-25`.
|
||||
- Added `--disable-playwright` flag to prevent Playwright installation prompts and usage, by Andrew Grigorev.
|
||||
- The `aider scrape` command-line tool will now use Playwright for web scraping if it is available, by Jon Keys.
|
||||
- Fixed linter command execution on Windows by adopting `oslex` for argument quoting, by Titusz Pan.
|
||||
- Improved cross-platform display of shell commands by using `oslex` for robust argument quoting, by Titusz Pan.
|
||||
- Aider wrote 46% of the code in this release.
|
||||
|
||||
### Aider v0.82.3
|
||||
|
||||
- Add support for `gemini-2.5-flash-preview-04-17` models.
|
||||
- Improved robustness of edit block parsing when filenames start with backticks or fences.
|
||||
- Add new `udiff-simple` edit format, for Gemini 2.5 Pro.
|
||||
|
@ -34,9 +56,8 @@ cog.out(text)
|
|||
- Fix parsing of diffs for newly created files (`--- /dev/null`).
|
||||
- Add markdown syntax highlighting support when editing multi-line commit messages via `/commit`, by Kay Gosho.
|
||||
- Set Gemini 2.5 Pro models to use the `overeager` prompt setting by default.
|
||||
- Add common file types (`.svg`, `.pdf`) and IDE directories (`.idea/`, `.vscode/`, etc.) to the default list of ignored files for AI comment scanning (`--watch`).
|
||||
- Add common file types (`.svg`, `.pdf`) to the default list of ignored files for AI comment scanning (`--watch`).
|
||||
- Skip scanning files larger than 1MB for AI comments (`--watch`).
|
||||
- Aider wrote 67% of the code in this release.
|
||||
|
||||
### Aider v0.82.2
|
||||
|
||||
|
|
|
@ -831,7 +831,7 @@
|
|||
date: 2025-04-12
|
||||
versions: 0.81.3.dev
|
||||
seconds_per_case: 45.3
|
||||
total_cost: 6.3174
|
||||
total_cost: 0 # incorrect: 6.3174
|
||||
|
||||
- dirname: 2025-03-29-05-24-55--chatgpt4o-mar28-diff
|
||||
test_cases: 225
|
||||
|
@ -1223,4 +1223,86 @@
|
|||
date: 2025-04-20
|
||||
versions: 0.82.3.dev
|
||||
seconds_per_case: 50.1
|
||||
total_cost: 1.8451
|
||||
total_cost: 1.8451
|
||||
|
||||
- dirname: 2025-05-07-19-32-40--gemini0506-diff-fenced-completion_cost
|
||||
test_cases: 225
|
||||
model: Gemini 2.5 Pro Preview 05-06
|
||||
edit_format: diff-fenced
|
||||
commit_hash: 3b08327-dirty
|
||||
pass_rate_1: 36.4
|
||||
pass_rate_2: 76.9
|
||||
pass_num_1: 82
|
||||
pass_num_2: 173
|
||||
percent_cases_well_formed: 97.3
|
||||
error_outputs: 15
|
||||
num_malformed_responses: 7
|
||||
num_with_malformed_responses: 6
|
||||
user_asks: 105
|
||||
lazy_comments: 0
|
||||
syntax_errors: 0
|
||||
indentation_errors: 0
|
||||
exhausted_context_windows: 0
|
||||
test_timeouts: 2
|
||||
total_tests: 225
|
||||
command: aider --model gemini/gemini-2.5-pro-preview-05-06
|
||||
date: 2025-05-07
|
||||
versions: 0.82.4.dev
|
||||
seconds_per_case: 165.3
|
||||
total_cost: 37.4104
|
||||
|
||||
- dirname: 2025-05-08-03-20-24--qwen3-32b-default
|
||||
test_cases: 225
|
||||
model: Qwen3 32B
|
||||
edit_format: diff
|
||||
commit_hash: aaacee5-dirty, aeaf259
|
||||
pass_rate_1: 14.2
|
||||
pass_rate_2: 40.0
|
||||
pass_num_1: 32
|
||||
pass_num_2: 90
|
||||
percent_cases_well_formed: 83.6
|
||||
error_outputs: 119
|
||||
num_malformed_responses: 50
|
||||
num_with_malformed_responses: 37
|
||||
user_asks: 97
|
||||
lazy_comments: 0
|
||||
syntax_errors: 0
|
||||
indentation_errors: 0
|
||||
exhausted_context_windows: 12
|
||||
prompt_tokens: 317591
|
||||
completion_tokens: 120418
|
||||
test_timeouts: 5
|
||||
total_tests: 225
|
||||
command: aider --model openrouter/qwen/qwen3-32b
|
||||
date: 2025-05-08
|
||||
versions: 0.82.4.dev
|
||||
seconds_per_case: 372.2
|
||||
total_cost: 0.7603
|
||||
|
||||
- dirname: 2025-05-08-03-22-37--qwen3-235b-defaults
|
||||
test_cases: 225
|
||||
model: Qwen3 235B A22B
|
||||
edit_format: diff
|
||||
commit_hash: aaacee5-dirty
|
||||
pass_rate_1: 17.3
|
||||
pass_rate_2: 49.8
|
||||
pass_num_1: 39
|
||||
pass_num_2: 112
|
||||
percent_cases_well_formed: 91.6
|
||||
error_outputs: 58
|
||||
num_malformed_responses: 29
|
||||
num_with_malformed_responses: 19
|
||||
user_asks: 102
|
||||
lazy_comments: 0
|
||||
syntax_errors: 0
|
||||
indentation_errors: 0
|
||||
exhausted_context_windows: 0
|
||||
prompt_tokens: 0
|
||||
completion_tokens: 0
|
||||
test_timeouts: 1
|
||||
total_tests: 225
|
||||
command: aider --model openrouter/qwen/qwen3-235b-a22b
|
||||
date: 2025-05-08
|
||||
versions: 0.82.4.dev
|
||||
seconds_per_case: 428.1
|
||||
total_cost: 1.8037
|
114
aider/website/_posts/2025-05-07-gemini-cost.md
Normal file
114
aider/website/_posts/2025-05-07-gemini-cost.md
Normal file
|
@ -0,0 +1,114 @@
|
|||
---
|
||||
title: Gemini 2.5 Pro Preview 03-25 benchmark cost
|
||||
excerpt: The $6.32 benchmark cost reported for Gemini 2.5 Pro Preview 03-25 was incorrect.
|
||||
draft: false
|
||||
nav_exclude: true
|
||||
---
|
||||
{% if page.date %}
|
||||
<p class="post-date">{{ page.date | date: "%B %d, %Y" }}</p>
|
||||
{% endif %}
|
||||
|
||||
# Gemini 2.5 Pro Preview 03-25 benchmark cost
|
||||
|
||||
## Summary
|
||||
The $6.32 cost reported to run the aider polyglot benchmark on
|
||||
Gemini 2.5 Pro Preview 03-25 was incorrect.
|
||||
The true cost was higher, possibly significantly so.
|
||||
The incorrect cost has been removed from the leaderboard.
|
||||
|
||||
An investigation determined the primary cause was that the litellm
|
||||
package (used by aider for LLM API connections) was not properly including reasoning tokens in
|
||||
the token counts it reported.
|
||||
While an incorrect price-per-token entry for the model also existed in litellm's cost
|
||||
database at that time, this was found not to be a contributing factor.
|
||||
Aider's own internal, correct pricing data was utilized during the benchmark.
|
||||
|
||||
## Resolution
|
||||
|
||||
Litellm began correctly including reasoning tokens in the reported counts
|
||||
on April 21, 2025 in
|
||||
commit [a7db0df](https://github.com/BerriAI/litellm/commit/a7db0df0434bfbac2b68ebe1c343b77955becb4b).
|
||||
This change was released in litellm v1.67.1.
|
||||
Aider picked up this change April 28, 2025 when it upgraded its litellm dependency
|
||||
from v1.65.7 to v1.67.4.post1
|
||||
in commit [9351f37](https://github.com/Aider-AI/aider/commit/9351f37).
|
||||
That dependency change shipped on May 5, 2025 in aider v0.82.3.
|
||||
|
||||
Unfortunately the 03-25 version of Gemini 2.5 Pro Preview is no longer available,
|
||||
so it is not possible to re-run the benchmark to obtain an accurate cost.
|
||||
As a possibly relevant comparison, the newer 05-06 version of Gemini 2.5 Pro Preview
|
||||
completed the benchmark at a cost of about $37.
|
||||
|
||||
## Investigation detail
|
||||
|
||||
The version of litellm available at that time of the benchmark appears to have been
|
||||
excluding reasoning tokens from the token counts it reported.
|
||||
So even though aider had correct per-token pricing, it did not have the correct token counts
|
||||
used during the benchmark.
|
||||
This resulted in an underestimate of the benchmark costs.
|
||||
|
||||
The incorrect litellm database entry does not appear to have affected the aider benchmark costs.
|
||||
Aider maintains and uses its own database of costs for some models, and it contained
|
||||
the correct pricing at the time of the benchmark.
|
||||
Aider appears to have
|
||||
loaded the correct cost data from its database and made use of it during the benchmark.
|
||||
|
||||
Every aider benchmark report contains the git commit hash of the aider repository state used to
|
||||
run the benchmark.
|
||||
The
|
||||
[benchmark run in question](https://github.com/Aider-AI/aider/blob/edbfec0ce4e1fe86735c915cb425b0d8636edc32/aider/website/_data/polyglot_leaderboard.yml#L814)
|
||||
was built from
|
||||
commit [0282574](https://github.com/Aider-AI/aider/commit/0282574).
|
||||
|
||||
Additional runs of the benchmark from that build verified that the error in litellm's
|
||||
model cost database appears not to have been a factor:
|
||||
|
||||
- Aider's internal model database correctly overrides the litellm database, which contained an incorrect token cost at the time.
|
||||
- The correct pricing is loaded from aider's internal model database and produces similar (incorrect) costs as the original run.
|
||||
- Updating aider's internal model database with an absurdly high token cost resulted in an appropriately high benchmark cost report, demonstrating that the internal database costs were in effect.
|
||||
|
||||
This specific build of aider was then updated with various versions of litellm using `git biset`
|
||||
to identify the first litellm commit where reasoning tokens counts were correctly reported.
|
||||
|
||||
|
||||
|
||||
## Timeline
|
||||
|
||||
Below is the full timeline of git commits related to this issue in the aider and litellm repositories.
|
||||
Each entry has a UTC timestamp, followed by the original literal timestamp obtained from the
|
||||
relevant source.
|
||||
|
||||
- 2025-04-04 19:54:45 UTC (Sat Apr 5 08:54:45 2025 +1300)
|
||||
- Correct value `"output_cost_per_token": 0.000010` for `gemini/gemini-2.5-pro-preview-03-25` added to `aider/resources/model-metadata.json`
|
||||
- Commit [eda796d](https://github.com/Aider-AI/aider/commit/eda796d) in aider.
|
||||
|
||||
- 2025-04-05 16:20:01 UTC (Sun Apr 6 00:20:01 2025 +0800)
|
||||
- First litellm commit of `gemini/gemini-2.5-pro-preview-03-25` metadata, with incorrect price `"output_cost_per_token": 0.0000010`
|
||||
- Commit [cd0a1e6](https://github.com/BerriAI/litellm/commit/cd0a1e6) in litellm.
|
||||
|
||||
- 2025-04-10 01:48:43 UTC (Wed Apr 9 18:48:43 2025 -0700)
|
||||
- litellm commit updates `gemini/gemini-2.5-pro-preview-03-25` metadata, but not price
|
||||
- Commit [ac4f32f](https://github.com/BerriAI/litellm/commit/ac4f32f) in litellm.
|
||||
|
||||
- 2025-04-12 04:55:50 UTC (2025-04-12-04-55-50 UTC)
|
||||
- Benchmark performed.
|
||||
- Aider repo hash [0282574 recorded in benchmark results](https://github.com/Aider-AI/aider/blob/7fbeafa1cfd4ad83f7499417837cdfa6b16fe7a1/aider/website/_data/polyglot_leaderboard.yml#L814), without a "dirty" annotation, indicating that the benchmark was run on a clean checkout of the aider repo at commit [0282574](https://github.com/Aider-AI/aider/commit/0282574).
|
||||
- Correct value `"output_cost_per_token": 0.000010` is in `aider/resources/model-metadata.json` at this commit [0282574](https://github.com/Aider-AI/aider/blob/0282574/aider/resources/model-metadata.json#L357).
|
||||
|
||||
- 2025-04-12 15:06:39 UTC (Apr 12 08:06:39 2025 -0700)
|
||||
- Benchmark results added to aider repo.
|
||||
- Commit [7fbeafa](https://github.com/Aider-AI/aider/commit/7fbeafa) in aider.
|
||||
|
||||
- 2025-04-12 15:20:04 UTC (Sat Apr 12 19:20:04 2025 +0400)
|
||||
- litellm commit fixes `gemini/gemini-2.5-pro-preview-03-25` price metadata to `"output_cost_per_token": 0.00001`
|
||||
- Commit [93037ea](https://github.com/BerriAI/litellm/commit/93037ea) in litellm.
|
||||
|
||||
- 2025-04-22 05:48:00 UTC (Mon Apr 21 22:48:00 2025 -0700)
|
||||
- Litellm started including reasoning tokens in token count reporting.
|
||||
- Commit [a7db0df](https://github.com/BerriAI/litellm/commit/a7db0df0434bfbac2b68ebe1c343b77955becb4b) in litellm.
|
||||
- This fix was released in litellm v1.67.1.
|
||||
|
||||
- 2025-04-28 14:53:20 UTC (Mon Apr 28 07:53:20 2025 -0700)
|
||||
- Aider upgraded its litellm dependency from v1.65.7 to v1.67.4.post1, which included the reasoning token count fix.
|
||||
- Commit [9351f37](https://github.com/Aider-AI/aider/commit/9351f37) in aider.
|
||||
- This dependency change shipped on May 5, 2025 in aider v0.82.3.
|
File diff suppressed because it is too large
Load diff
|
@ -224,11 +224,11 @@
|
|||
## Enable/disable commits when repo is found dirty (default: True)
|
||||
#dirty-commits: true
|
||||
|
||||
## Attribute aider code changes in the git author name (default: True)
|
||||
#attribute-author: true
|
||||
## Attribute aider code changes in the git author name (default: True). If explicitly set to True, overrides --attribute-co-authored-by precedence.
|
||||
#attribute-author: xxx
|
||||
|
||||
## Attribute aider commits in the git committer name (default: True)
|
||||
#attribute-committer: true
|
||||
## Attribute aider commits in the git committer name (default: True). If explicitly set to True, overrides --attribute-co-authored-by precedence for aider edits.
|
||||
#attribute-committer: xxx
|
||||
|
||||
## Prefix commit messages with 'aider: ' if aider authored the changes (default: False)
|
||||
#attribute-commit-message-author: false
|
||||
|
@ -236,6 +236,9 @@
|
|||
## Prefix all commit messages with 'aider: ' (default: False)
|
||||
#attribute-commit-message-committer: false
|
||||
|
||||
## Attribute aider edits using the Co-authored-by trailer in the commit message (default: False). If True, this takes precedence over default --attribute-author and --attribute-committer behavior unless they are explicitly set to True.
|
||||
#attribute-co-authored-by: false
|
||||
|
||||
## Enable/disable git pre-commit hooks with --no-verify (default: False)
|
||||
#git-commit-verify: false
|
||||
|
||||
|
@ -358,6 +361,9 @@
|
|||
#################
|
||||
# Other settings:
|
||||
|
||||
## Never prompt for or attempt to install Playwright for web scraping (default: False).
|
||||
#disable-playwright: false
|
||||
|
||||
## specify a file to edit (can be used multiple times)
|
||||
#file: xxx
|
||||
## Specify multiple values like this:
|
||||
|
|
|
@ -213,11 +213,11 @@
|
|||
## Enable/disable commits when repo is found dirty (default: True)
|
||||
#AIDER_DIRTY_COMMITS=true
|
||||
|
||||
## Attribute aider code changes in the git author name (default: True)
|
||||
#AIDER_ATTRIBUTE_AUTHOR=true
|
||||
## Attribute aider code changes in the git author name (default: True). If explicitly set to True, overrides --attribute-co-authored-by precedence.
|
||||
#AIDER_ATTRIBUTE_AUTHOR=
|
||||
|
||||
## Attribute aider commits in the git committer name (default: True)
|
||||
#AIDER_ATTRIBUTE_COMMITTER=true
|
||||
## Attribute aider commits in the git committer name (default: True). If explicitly set to True, overrides --attribute-co-authored-by precedence for aider edits.
|
||||
#AIDER_ATTRIBUTE_COMMITTER=
|
||||
|
||||
## Prefix commit messages with 'aider: ' if aider authored the changes (default: False)
|
||||
#AIDER_ATTRIBUTE_COMMIT_MESSAGE_AUTHOR=false
|
||||
|
@ -225,6 +225,9 @@
|
|||
## Prefix all commit messages with 'aider: ' (default: False)
|
||||
#AIDER_ATTRIBUTE_COMMIT_MESSAGE_COMMITTER=false
|
||||
|
||||
## Attribute aider edits using the Co-authored-by trailer in the commit message (default: False). If True, this takes precedence over default --attribute-author and --attribute-committer behavior unless they are explicitly set to True.
|
||||
#AIDER_ATTRIBUTE_CO_AUTHORED_BY=false
|
||||
|
||||
## Enable/disable git pre-commit hooks with --no-verify (default: False)
|
||||
#AIDER_GIT_COMMIT_VERIFY=false
|
||||
|
||||
|
@ -339,6 +342,9 @@
|
|||
#################
|
||||
# Other settings:
|
||||
|
||||
## Never prompt for or attempt to install Playwright for web scraping (default: False).
|
||||
#AIDER_DISABLE_PLAYWRIGHT=false
|
||||
|
||||
## specify a file to edit (can be used multiple times)
|
||||
#AIDER_FILE=
|
||||
|
||||
|
|
|
@ -644,6 +644,7 @@ cog.out("```\n")
|
|||
edit_format: diff
|
||||
use_repo_map: true
|
||||
accepts_settings:
|
||||
- reasoning_effort
|
||||
- thinking_tokens
|
||||
|
||||
- name: gemini/gemini-1.5-flash-002
|
||||
|
@ -678,6 +679,7 @@ cog.out("```\n")
|
|||
edit_format: diff
|
||||
use_repo_map: true
|
||||
accepts_settings:
|
||||
- reasoning_effort
|
||||
- thinking_tokens
|
||||
|
||||
- name: gemini/gemini-2.5-pro-exp-03-25
|
||||
|
@ -692,6 +694,12 @@ cog.out("```\n")
|
|||
use_repo_map: true
|
||||
overeager: true
|
||||
|
||||
- name: gemini/gemini-2.5-pro-preview-05-06
|
||||
edit_format: diff-fenced
|
||||
weak_model_name: gemini/gemini-2.5-flash-preview-04-17
|
||||
use_repo_map: true
|
||||
overeager: true
|
||||
|
||||
- name: gemini/gemini-exp-1114
|
||||
edit_format: diff
|
||||
use_repo_map: true
|
||||
|
@ -1222,6 +1230,18 @@ cog.out("```\n")
|
|||
use_repo_map: true
|
||||
overeager: true
|
||||
|
||||
- name: openrouter/google/gemini-2.5-pro-preview-03-25
|
||||
edit_format: diff-fenced
|
||||
weak_model_name: openrouter/google/gemini-2.0-flash-001
|
||||
use_repo_map: true
|
||||
overeager: true
|
||||
|
||||
- name: openrouter/google/gemini-2.5-pro-preview-05-06
|
||||
edit_format: diff-fenced
|
||||
weak_model_name: openrouter/google/gemini-2.0-flash-001
|
||||
use_repo_map: true
|
||||
overeager: true
|
||||
|
||||
- name: openrouter/google/gemma-3-27b-it
|
||||
use_system_prompt: false
|
||||
|
||||
|
@ -1431,6 +1451,7 @@ cog.out("```\n")
|
|||
edit_format: diff
|
||||
use_repo_map: true
|
||||
accepts_settings:
|
||||
- reasoning_effort
|
||||
- thinking_tokens
|
||||
|
||||
- name: vertex_ai/claude-3-5-haiku@20241022
|
||||
|
@ -1495,6 +1516,13 @@ cog.out("```\n")
|
|||
overeager: true
|
||||
editor_model_name: vertex_ai-language-models/gemini-2.5-flash-preview-04-17
|
||||
|
||||
- name: vertex_ai/gemini-2.5-pro-preview-05-06
|
||||
edit_format: diff-fenced
|
||||
weak_model_name: vertex_ai-language-models/gemini-2.5-flash-preview-04-17
|
||||
use_repo_map: true
|
||||
overeager: true
|
||||
editor_model_name: vertex_ai-language-models/gemini-2.5-flash-preview-04-17
|
||||
|
||||
- name: vertex_ai/gemini-pro-experimental
|
||||
edit_format: diff-fenced
|
||||
use_repo_map: true
|
||||
|
|
|
@ -278,11 +278,11 @@ cog.outl("```")
|
|||
## Enable/disable commits when repo is found dirty (default: True)
|
||||
#dirty-commits: true
|
||||
|
||||
## Attribute aider code changes in the git author name (default: True)
|
||||
#attribute-author: true
|
||||
## Attribute aider code changes in the git author name (default: True). If explicitly set to True, overrides --attribute-co-authored-by precedence.
|
||||
#attribute-author: xxx
|
||||
|
||||
## Attribute aider commits in the git committer name (default: True)
|
||||
#attribute-committer: true
|
||||
## Attribute aider commits in the git committer name (default: True). If explicitly set to True, overrides --attribute-co-authored-by precedence for aider edits.
|
||||
#attribute-committer: xxx
|
||||
|
||||
## Prefix commit messages with 'aider: ' if aider authored the changes (default: False)
|
||||
#attribute-commit-message-author: false
|
||||
|
@ -290,6 +290,9 @@ cog.outl("```")
|
|||
## Prefix all commit messages with 'aider: ' (default: False)
|
||||
#attribute-commit-message-committer: false
|
||||
|
||||
## Attribute aider edits using the Co-authored-by trailer in the commit message (default: False). If True, this takes precedence over default --attribute-author and --attribute-committer behavior unless they are explicitly set to True.
|
||||
#attribute-co-authored-by: false
|
||||
|
||||
## Enable/disable git pre-commit hooks with --no-verify (default: False)
|
||||
#git-commit-verify: false
|
||||
|
||||
|
@ -412,6 +415,9 @@ cog.outl("```")
|
|||
#################
|
||||
# Other settings:
|
||||
|
||||
## Never prompt for or attempt to install Playwright for web scraping (default: False).
|
||||
#disable-playwright: false
|
||||
|
||||
## specify a file to edit (can be used multiple times)
|
||||
#file: xxx
|
||||
## Specify multiple values like this:
|
||||
|
|
|
@ -253,11 +253,11 @@ cog.outl("```")
|
|||
## Enable/disable commits when repo is found dirty (default: True)
|
||||
#AIDER_DIRTY_COMMITS=true
|
||||
|
||||
## Attribute aider code changes in the git author name (default: True)
|
||||
#AIDER_ATTRIBUTE_AUTHOR=true
|
||||
## Attribute aider code changes in the git author name (default: True). If explicitly set to True, overrides --attribute-co-authored-by precedence.
|
||||
#AIDER_ATTRIBUTE_AUTHOR=
|
||||
|
||||
## Attribute aider commits in the git committer name (default: True)
|
||||
#AIDER_ATTRIBUTE_COMMITTER=true
|
||||
## Attribute aider commits in the git committer name (default: True). If explicitly set to True, overrides --attribute-co-authored-by precedence for aider edits.
|
||||
#AIDER_ATTRIBUTE_COMMITTER=
|
||||
|
||||
## Prefix commit messages with 'aider: ' if aider authored the changes (default: False)
|
||||
#AIDER_ATTRIBUTE_COMMIT_MESSAGE_AUTHOR=false
|
||||
|
@ -265,6 +265,9 @@ cog.outl("```")
|
|||
## Prefix all commit messages with 'aider: ' (default: False)
|
||||
#AIDER_ATTRIBUTE_COMMIT_MESSAGE_COMMITTER=false
|
||||
|
||||
## Attribute aider edits using the Co-authored-by trailer in the commit message (default: False). If True, this takes precedence over default --attribute-author and --attribute-committer behavior unless they are explicitly set to True.
|
||||
#AIDER_ATTRIBUTE_CO_AUTHORED_BY=false
|
||||
|
||||
## Enable/disable git pre-commit hooks with --no-verify (default: False)
|
||||
#AIDER_GIT_COMMIT_VERIFY=false
|
||||
|
||||
|
@ -379,6 +382,9 @@ cog.outl("```")
|
|||
#################
|
||||
# Other settings:
|
||||
|
||||
## Never prompt for or attempt to install Playwright for web scraping (default: False).
|
||||
#AIDER_DISABLE_PLAYWRIGHT=false
|
||||
|
||||
## specify a file to edit (can be used multiple times)
|
||||
#AIDER_FILE=
|
||||
|
||||
|
|
|
@ -80,8 +80,8 @@ for alias, model in sorted(MODEL_ALIASES.items()):
|
|||
- `4o`: gpt-4o
|
||||
- `deepseek`: deepseek/deepseek-chat
|
||||
- `flash`: gemini/gemini-2.5-flash-preview-04-17
|
||||
- `gemini`: gemini/gemini-2.5-pro-preview-03-25
|
||||
- `gemini-2.5-pro`: gemini/gemini-2.5-pro-exp-03-25
|
||||
- `gemini`: gemini/gemini-2.5-pro-preview-05-06
|
||||
- `gemini-2.5-pro`: gemini/gemini-2.5-pro-preview-05-06
|
||||
- `gemini-exp`: gemini/gemini-2.5-pro-exp-03-25
|
||||
- `grok3`: xai/grok-3-beta
|
||||
- `haiku`: claude-3-5-haiku-20241022
|
||||
|
|
|
@ -56,6 +56,7 @@ usage: aider [-h] [--model] [--openai-api-key] [--anthropic-api-key]
|
|||
[--attribute-committer | --no-attribute-committer]
|
||||
[--attribute-commit-message-author | --no-attribute-commit-message-author]
|
||||
[--attribute-commit-message-committer | --no-attribute-commit-message-committer]
|
||||
[--attribute-co-authored-by | --no-attribute-co-authored-by]
|
||||
[--git-commit-verify | --no-git-commit-verify]
|
||||
[--commit] [--commit-prompt] [--dry-run | --no-dry-run]
|
||||
[--skip-sanity-check-repo]
|
||||
|
@ -72,9 +73,10 @@ usage: aider [-h] [--model] [--openai-api-key] [--anthropic-api-key]
|
|||
[--copy-paste | --no-copy-paste] [--apply]
|
||||
[--apply-clipboard-edits] [--exit] [--show-repo-map]
|
||||
[--show-prompts] [--voice-format] [--voice-language]
|
||||
[--voice-input-device] [--file] [--read] [--vim]
|
||||
[--chat-language] [--yes-always] [-v] [--load]
|
||||
[--encoding] [--line-endings] [-c] [--env-file]
|
||||
[--voice-input-device] [--disable-playwright] [--file]
|
||||
[--read] [--vim] [--chat-language] [--yes-always] [-v]
|
||||
[--load] [--encoding] [--line-endings] [-c]
|
||||
[--env-file]
|
||||
[--suggest-shell-commands | --no-suggest-shell-commands]
|
||||
[--fancy-input | --no-fancy-input]
|
||||
[--multiline | --no-multiline]
|
||||
|
@ -412,16 +414,14 @@ Aliases:
|
|||
- `--no-dirty-commits`
|
||||
|
||||
### `--attribute-author`
|
||||
Attribute aider code changes in the git author name (default: True)
|
||||
Default: True
|
||||
Attribute aider code changes in the git author name (default: True). If explicitly set to True, overrides --attribute-co-authored-by precedence.
|
||||
Environment variable: `AIDER_ATTRIBUTE_AUTHOR`
|
||||
Aliases:
|
||||
- `--attribute-author`
|
||||
- `--no-attribute-author`
|
||||
|
||||
### `--attribute-committer`
|
||||
Attribute aider commits in the git committer name (default: True)
|
||||
Default: True
|
||||
Attribute aider commits in the git committer name (default: True). If explicitly set to True, overrides --attribute-co-authored-by precedence for aider edits.
|
||||
Environment variable: `AIDER_ATTRIBUTE_COMMITTER`
|
||||
Aliases:
|
||||
- `--attribute-committer`
|
||||
|
@ -443,6 +443,14 @@ Aliases:
|
|||
- `--attribute-commit-message-committer`
|
||||
- `--no-attribute-commit-message-committer`
|
||||
|
||||
### `--attribute-co-authored-by`
|
||||
Attribute aider edits using the Co-authored-by trailer in the commit message (default: False). If True, this takes precedence over default --attribute-author and --attribute-committer behavior unless they are explicitly set to True.
|
||||
Default: False
|
||||
Environment variable: `AIDER_ATTRIBUTE_CO_AUTHORED_BY`
|
||||
Aliases:
|
||||
- `--attribute-co-authored-by`
|
||||
- `--no-attribute-co-authored-by`
|
||||
|
||||
### `--git-commit-verify`
|
||||
Enable/disable git pre-commit hooks with --no-verify (default: False)
|
||||
Default: False
|
||||
|
@ -652,6 +660,11 @@ Environment variable: `AIDER_VOICE_INPUT_DEVICE`
|
|||
|
||||
## Other settings:
|
||||
|
||||
### `--disable-playwright`
|
||||
Never prompt for or attempt to install Playwright for web scraping (default: False).
|
||||
Default: False
|
||||
Environment variable: `AIDER_DISABLE_PLAYWRIGHT`
|
||||
|
||||
### `--file FILE`
|
||||
specify a file to edit (can be used multiple times)
|
||||
Environment variable: `AIDER_FILE`
|
||||
|
|
|
@ -264,15 +264,12 @@ tr:hover { background-color: #f5f5f5; }
|
|||
</style>
|
||||
<table>
|
||||
<tr><th>Model Name</th><th class='right'>Total Tokens</th><th class='right'>Percent</th></tr>
|
||||
<tr><td>gemini/gemini-2.5-pro-exp-03-25</td><td class='right'>1,826,168</td><td class='right'>83.3%</td></tr>
|
||||
<tr><td>o3</td><td class='right'>239,619</td><td class='right'>10.9%</td></tr>
|
||||
<tr><td>openrouter/anthropic/claude-3.7-sonnet</td><td class='right'>56,318</td><td class='right'>2.6%</td></tr>
|
||||
<tr><td>gemini/gemini-2.5-flash-preview-04-17</td><td class='right'>18,645</td><td class='right'>0.9%</td></tr>
|
||||
<tr><td>gemini/gemini-2.5-pro-preview-03-25</td><td class='right'>16,524</td><td class='right'>0.8%</td></tr>
|
||||
<tr><td>o4-mini</td><td class='right'>16,499</td><td class='right'>0.8%</td></tr>
|
||||
<tr><td>xai/grok-3-fast-beta</td><td class='right'>10,288</td><td class='right'>0.5%</td></tr>
|
||||
<tr><td>None</td><td class='right'>8,001</td><td class='right'>0.4%</td></tr>
|
||||
<tr><td>gemini/REDACTED</td><td class='right'>606</td><td class='right'>0.0%</td></tr>
|
||||
<tr><td>gemini/gemini-2.5-pro-exp-03-25</td><td class='right'>668,483</td><td class='right'>55.7%</td></tr>
|
||||
<tr><td>gemini/gemini-2.5-pro-preview-03-25</td><td class='right'>442,019</td><td class='right'>36.9%</td></tr>
|
||||
<tr><td>o3</td><td class='right'>52,666</td><td class='right'>4.4%</td></tr>
|
||||
<tr><td>gemini/gemini-2.5-pro-preview-05-06</td><td class='right'>18,654</td><td class='right'>1.6%</td></tr>
|
||||
<tr><td>openrouter/REDACTED</td><td class='right'>15,587</td><td class='right'>1.3%</td></tr>
|
||||
<tr><td>gemini/REDACTED</td><td class='right'>1,989</td><td class='right'>0.2%</td></tr>
|
||||
</table>
|
||||
|
||||
{: .note :}
|
||||
|
|
|
@ -71,4 +71,6 @@ Additionally, you can use the following options to prefix commit messages:
|
|||
- `--attribute-commit-message-author`: Prefix commit messages with 'aider: ' if aider authored the changes.
|
||||
- `--attribute-commit-message-committer`: Prefix all commit messages with 'aider: ', regardless of whether aider authored the changes or not.
|
||||
|
||||
Both of these options are disabled by default, but can be useful for easily identifying changes made by aider.
|
||||
Finally, you can use `--attribute-co-authored-by` to have aider append a Co-authored-by trailer to the end of the commit string.
|
||||
This will disable appending `(aider)` to the git author and git committer unless you have explicitly enabled those settings.
|
||||
|
||||
|
|
|
@ -180,6 +180,8 @@ cog.out(get_supported_languages_md())
|
|||
| nix | .nix | | ✓ |
|
||||
| nqc | .nqc | | ✓ |
|
||||
| objc | .mm | | ✓ |
|
||||
| ocaml | .ml | ✓ | ✓ |
|
||||
| ocaml_interface | .mli | ✓ | ✓ |
|
||||
| odin | .odin | | ✓ |
|
||||
| org | .org | | ✓ |
|
||||
| pascal | .pas | | ✓ |
|
||||
|
|
|
@ -285,6 +285,6 @@ mod_dates = [get_last_modified_date(file) for file in files]
|
|||
latest_mod_date = max(mod_dates)
|
||||
cog.out(f"{latest_mod_date.strftime('%B %d, %Y.')}")
|
||||
]]]-->
|
||||
April 20, 2025.
|
||||
May 08, 2025.
|
||||
<!--[[[end]]]-->
|
||||
</p>
|
||||
|
|
|
@ -78,6 +78,7 @@ cog.out(''.join(lines))
|
|||
- GEMINI_API_KEY
|
||||
- GROQ_API_KEY
|
||||
- HUGGINGFACE_API_KEY
|
||||
- INFINITY_API_KEY
|
||||
- MARITALK_API_KEY
|
||||
- MISTRAL_API_KEY
|
||||
- NLP_CLOUD_API_KEY
|
||||
|
|
|
@ -69,11 +69,11 @@ cog.out(text)
|
|||
]]]-->
|
||||
<a href="https://github.com/Aider-AI/aider" class="github-badge badge-stars" title="Total number of GitHub stars the Aider project has received">
|
||||
<span class="badge-label">⭐ GitHub Stars</span>
|
||||
<span class="badge-value">32K</span>
|
||||
<span class="badge-value">33K</span>
|
||||
</a>
|
||||
<a href="https://pypi.org/project/aider-chat/" class="github-badge badge-installs" title="Total number of installations via pip from PyPI">
|
||||
<span class="badge-label">📦 Installs</span>
|
||||
<span class="badge-value">2.1M</span>
|
||||
<span class="badge-value">2.2M</span>
|
||||
</a>
|
||||
<div class="github-badge badge-tokens" title="Number of tokens processed weekly by Aider users">
|
||||
<span class="badge-label">📈 Tokens/week</span>
|
||||
|
|
|
@ -492,6 +492,8 @@ def summarize_results(dirname, stats_languages=None):
|
|||
res.syntax_errors = 0
|
||||
res.indentation_errors = 0
|
||||
res.lazy_comments = 0
|
||||
res.prompt_tokens = 0
|
||||
res.completion_tokens = 0
|
||||
|
||||
res.reasoning_effort = None
|
||||
res.thinking_tokens = None
|
||||
|
@ -523,6 +525,9 @@ def summarize_results(dirname, stats_languages=None):
|
|||
res.syntax_errors += results.get("syntax_errors", 0)
|
||||
res.indentation_errors += results.get("indentation_errors", 0)
|
||||
|
||||
res.prompt_tokens += results.get("prompt_tokens", 0)
|
||||
res.completion_tokens += results.get("completion_tokens", 0)
|
||||
|
||||
res.reasoning_effort = results.get("reasoning_effort")
|
||||
res.thinking_tokens = results.get("thinking_tokens")
|
||||
|
||||
|
@ -590,6 +595,8 @@ def summarize_results(dirname, stats_languages=None):
|
|||
show("syntax_errors")
|
||||
show("indentation_errors")
|
||||
show("exhausted_context_windows")
|
||||
show("prompt_tokens", red=None)
|
||||
show("completion_tokens", red=None)
|
||||
show("test_timeouts")
|
||||
print(f" total_tests: {res.total_tests}")
|
||||
|
||||
|
@ -950,6 +957,8 @@ def run_test_real(
|
|||
indentation_errors=indentation_errors,
|
||||
lazy_comments=lazy_comments, # Add the count of pattern matches to the results
|
||||
reasoning_effort=reasoning_effort,
|
||||
prompt_tokens=coder.total_tokens_sent,
|
||||
completion_tokens=coder.total_tokens_received,
|
||||
thinking_tokens=thinking_tokens,
|
||||
chat_hashes=list(
|
||||
zip(
|
||||
|
|
|
@ -120,7 +120,7 @@ google-api-python-client==2.169.0
|
|||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# google-generativeai
|
||||
google-auth==2.40.0
|
||||
google-auth==2.40.1
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# google-ai-generativelanguage
|
||||
|
@ -141,7 +141,7 @@ googleapis-common-protos==1.70.0
|
|||
# -c requirements/common-constraints.txt
|
||||
# google-api-core
|
||||
# grpcio-status
|
||||
grep-ast==0.8.1
|
||||
grep-ast==0.9.0
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# -r requirements/requirements.in
|
||||
|
@ -159,6 +159,10 @@ h11==0.16.0
|
|||
# -c requirements/common-constraints.txt
|
||||
# httpcore
|
||||
# uvicorn
|
||||
hf-xet==1.1.0
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# huggingface-hub
|
||||
httpcore==1.0.9
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
|
@ -178,7 +182,7 @@ httpx-sse==0.4.0
|
|||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# mcp
|
||||
huggingface-hub==0.30.2
|
||||
huggingface-hub==0.31.1
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# tokenizers
|
||||
|
@ -219,7 +223,7 @@ jsonschema-specifications==2025.4.1
|
|||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# jsonschema
|
||||
litellm==1.68.0
|
||||
litellm==1.68.1
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# -r requirements/requirements.in
|
||||
|
@ -247,6 +251,10 @@ mixpanel==4.10.1
|
|||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# -r requirements/requirements.in
|
||||
mslex==1.3.0
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# oslex
|
||||
multidict==6.4.3
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
|
@ -265,6 +273,10 @@ openai==1.75.0
|
|||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# litellm
|
||||
oslex==0.1.3
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# -r requirements/requirements.in
|
||||
packaging==24.2
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
|
@ -283,10 +295,6 @@ pillow==11.2.1
|
|||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# -r requirements/requirements.in
|
||||
pip==25.1.1
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# -r requirements/requirements.in
|
||||
posthog==4.0.1
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
|
@ -492,7 +500,7 @@ tree-sitter-embedded-template==0.23.2
|
|||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# tree-sitter-language-pack
|
||||
tree-sitter-language-pack==0.7.2
|
||||
tree-sitter-language-pack==0.7.3
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# grep-ast
|
||||
|
|
|
@ -135,7 +135,7 @@ google-api-core[grpc]==2.24.2
|
|||
# google-generativeai
|
||||
google-api-python-client==2.169.0
|
||||
# via google-generativeai
|
||||
google-auth==2.40.0
|
||||
google-auth==2.40.1
|
||||
# via
|
||||
# google-ai-generativelanguage
|
||||
# google-api-core
|
||||
|
@ -164,7 +164,7 @@ greenlet==3.2.1
|
|||
# via
|
||||
# playwright
|
||||
# sqlalchemy
|
||||
grep-ast==0.8.1
|
||||
grep-ast==0.9.0
|
||||
# via -r requirements/requirements.in
|
||||
griffe==1.7.3
|
||||
# via banks
|
||||
|
@ -178,6 +178,8 @@ h11==0.16.0
|
|||
# via
|
||||
# httpcore
|
||||
# uvicorn
|
||||
hf-xet==1.1.0
|
||||
# via huggingface-hub
|
||||
httpcore==1.0.9
|
||||
# via httpx
|
||||
httplib2==0.22.0
|
||||
|
@ -240,7 +242,7 @@ jsonschema-specifications==2025.4.1
|
|||
# via jsonschema
|
||||
kiwisolver==1.4.8
|
||||
# via matplotlib
|
||||
litellm==1.68.0
|
||||
litellm==1.68.1
|
||||
# via -r requirements/requirements.in
|
||||
llama-index-core==0.12.26
|
||||
# via
|
||||
|
@ -268,6 +270,8 @@ mixpanel==4.10.1
|
|||
# via -r requirements/requirements.in
|
||||
mpmath==1.3.0
|
||||
# via sympy
|
||||
mslex==1.3.0
|
||||
# via oslex
|
||||
multidict==6.4.3
|
||||
# via
|
||||
# aiohttp
|
||||
|
@ -304,6 +308,8 @@ numpy==1.26.4
|
|||
# transformers
|
||||
openai==1.75.0
|
||||
# via litellm
|
||||
oslex==0.1.3
|
||||
# via -r requirements/requirements.in
|
||||
packaging==24.2
|
||||
# via
|
||||
# -r requirements/requirements.in
|
||||
|
@ -336,12 +342,10 @@ pillow==11.2.1
|
|||
# sentence-transformers
|
||||
# streamlit
|
||||
pip==25.1.1
|
||||
# via
|
||||
# -r requirements/requirements.in
|
||||
# pip-tools
|
||||
# via pip-tools
|
||||
pip-tools==7.4.1
|
||||
# via -r requirements/requirements-dev.in
|
||||
platformdirs==4.3.7
|
||||
platformdirs==4.3.8
|
||||
# via
|
||||
# banks
|
||||
# virtualenv
|
||||
|
@ -569,7 +573,7 @@ tree-sitter-c-sharp==0.23.1
|
|||
# via tree-sitter-language-pack
|
||||
tree-sitter-embedded-template==0.23.2
|
||||
# via tree-sitter-language-pack
|
||||
tree-sitter-language-pack==0.7.2
|
||||
tree-sitter-language-pack==0.7.3
|
||||
# via grep-ast
|
||||
tree-sitter-yaml==0.7.0
|
||||
# via tree-sitter-language-pack
|
||||
|
@ -611,7 +615,7 @@ urllib3==2.4.0
|
|||
# via
|
||||
# mixpanel
|
||||
# requests
|
||||
uv==0.7.2
|
||||
uv==0.7.3
|
||||
# via -r requirements/requirements-dev.in
|
||||
uvicorn==0.34.2
|
||||
# via mcp
|
||||
|
|
|
@ -63,7 +63,7 @@ google-api-core[grpc]==2.24.2
|
|||
# -c requirements/common-constraints.txt
|
||||
# google-cloud-bigquery
|
||||
# google-cloud-core
|
||||
google-auth==2.40.0
|
||||
google-auth==2.40.1
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# google-api-core
|
||||
|
@ -176,7 +176,7 @@ pip-tools==7.4.1
|
|||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# -r requirements/requirements-dev.in
|
||||
platformdirs==4.3.7
|
||||
platformdirs==4.3.8
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# virtualenv
|
||||
|
@ -297,7 +297,7 @@ urllib3==2.4.0
|
|||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# requests
|
||||
uv==0.7.2
|
||||
uv==0.7.3
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# -r requirements/requirements-dev.in
|
||||
|
|
|
@ -93,6 +93,10 @@ h11==0.16.0
|
|||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# httpcore
|
||||
hf-xet==1.1.0
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# huggingface-hub
|
||||
httpcore==1.0.9
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
|
@ -101,7 +105,7 @@ httpx==0.28.1
|
|||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# llama-index-core
|
||||
huggingface-hub[inference]==0.30.2
|
||||
huggingface-hub[inference]==0.31.1
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# llama-index-embeddings-huggingface
|
||||
|
@ -187,7 +191,7 @@ pillow==11.2.1
|
|||
# -c requirements/common-constraints.txt
|
||||
# llama-index-core
|
||||
# sentence-transformers
|
||||
platformdirs==4.3.7
|
||||
platformdirs==4.3.8
|
||||
# via
|
||||
# -c requirements/common-constraints.txt
|
||||
# banks
|
||||
|
|
|
@ -26,8 +26,8 @@ json5
|
|||
psutil
|
||||
watchfiles
|
||||
socksio
|
||||
pip
|
||||
pillow
|
||||
oslex
|
||||
google-generativeai
|
||||
mcp>=1.0.0
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import os
|
||||
import unittest
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
|
@ -36,6 +37,16 @@ class TestLinter(unittest.TestCase):
|
|||
result = self.linter.run_cmd("test_cmd", "test_file.py", "code")
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_run_cmd_win(self):
|
||||
if os.name != "nt":
|
||||
self.skipTest("This test only runs on Windows")
|
||||
from pathlib import Path
|
||||
|
||||
root = Path(__file__).parent.parent.parent.absolute().as_posix()
|
||||
linter = Linter(encoding="utf-8", root=root)
|
||||
result = linter.run_cmd("dir", "tests\\basic", "code")
|
||||
self.assertIsNone(result)
|
||||
|
||||
@patch("subprocess.Popen")
|
||||
def test_run_cmd_with_errors(self, mock_popen):
|
||||
mock_process = MagicMock()
|
||||
|
@ -47,6 +58,27 @@ class TestLinter(unittest.TestCase):
|
|||
self.assertIsNotNone(result)
|
||||
self.assertIn("Error message", result.text)
|
||||
|
||||
def test_run_cmd_with_special_chars(self):
|
||||
with patch("subprocess.Popen") as mock_popen:
|
||||
mock_process = MagicMock()
|
||||
mock_process.returncode = 1
|
||||
mock_process.stdout.read.side_effect = ("Error message", None)
|
||||
mock_popen.return_value = mock_process
|
||||
|
||||
# Test with a file path containing special characters
|
||||
special_path = "src/(main)/product/[id]/page.tsx"
|
||||
result = self.linter.run_cmd("eslint", special_path, "code")
|
||||
|
||||
# Verify that the command was constructed correctly
|
||||
mock_popen.assert_called_once()
|
||||
call_args = mock_popen.call_args[0][0]
|
||||
|
||||
self.assertIn(special_path, call_args)
|
||||
|
||||
# The result should contain the error message
|
||||
self.assertIsNotNone(result)
|
||||
self.assertIn("Error message", result.text)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
|
@ -4,7 +4,7 @@ import tempfile
|
|||
import time
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import git
|
||||
|
||||
|
@ -165,14 +165,11 @@ class TestRepo(unittest.TestCase):
|
|||
args = mock_send.call_args[0] # Get positional args
|
||||
self.assertEqual(args[0][0]["content"], custom_prompt) # Check first message content
|
||||
|
||||
@unittest.skipIf(platform.system() == "Windows", "Git env var behavior differs on Windows")
|
||||
@patch("aider.repo.GitRepo.get_commit_message")
|
||||
def test_commit_with_custom_committer_name(self, mock_send):
|
||||
mock_send.return_value = '"a good commit message"'
|
||||
|
||||
# Cleanup of the git temp dir explodes on windows
|
||||
if platform.system() == "Windows":
|
||||
return
|
||||
|
||||
with GitTemporaryDirectory():
|
||||
# new repo
|
||||
raw_repo = git.Repo()
|
||||
|
@ -185,32 +182,192 @@ class TestRepo(unittest.TestCase):
|
|||
raw_repo.git.commit("-m", "initial commit")
|
||||
|
||||
io = InputOutput()
|
||||
git_repo = GitRepo(io, None, None)
|
||||
# Initialize GitRepo with default None values for attributes
|
||||
git_repo = GitRepo(io, None, None, attribute_author=None, attribute_committer=None)
|
||||
|
||||
# commit a change
|
||||
# commit a change with aider_edits=True (using default attributes)
|
||||
fname.write_text("new content")
|
||||
git_repo.commit(fnames=[str(fname)], aider_edits=True)
|
||||
commit_result = git_repo.commit(fnames=[str(fname)], aider_edits=True)
|
||||
self.assertIsNotNone(commit_result)
|
||||
|
||||
# check the committer name
|
||||
# check the committer name (defaults interpreted as True)
|
||||
commit = raw_repo.head.commit
|
||||
self.assertEqual(commit.author.name, "Test User (aider)")
|
||||
self.assertEqual(commit.committer.name, "Test User (aider)")
|
||||
|
||||
# commit a change without aider_edits
|
||||
# commit a change without aider_edits (using default attributes)
|
||||
fname.write_text("new content again!")
|
||||
git_repo.commit(fnames=[str(fname)], aider_edits=False)
|
||||
commit_result = git_repo.commit(fnames=[str(fname)], aider_edits=False)
|
||||
self.assertIsNotNone(commit_result)
|
||||
|
||||
# check the committer name
|
||||
# check the committer name (author not modified, committer still modified by default)
|
||||
commit = raw_repo.head.commit
|
||||
self.assertEqual(commit.author.name, "Test User")
|
||||
self.assertEqual(commit.committer.name, "Test User (aider)")
|
||||
|
||||
# Now test with explicit False
|
||||
git_repo_explicit_false = GitRepo(io, None, None, attribute_author=False, attribute_committer=False)
|
||||
fname.write_text("explicit false content")
|
||||
commit_result = git_repo_explicit_false.commit(fnames=[str(fname)], aider_edits=True)
|
||||
self.assertIsNotNone(commit_result)
|
||||
commit = raw_repo.head.commit
|
||||
self.assertEqual(commit.author.name, "Test User") # Explicit False
|
||||
self.assertEqual(commit.committer.name, "Test User") # Explicit False
|
||||
|
||||
# check that the original committer name is restored
|
||||
original_committer_name = os.environ.get("GIT_COMMITTER_NAME")
|
||||
self.assertIsNone(original_committer_name)
|
||||
original_author_name = os.environ.get("GIT_AUTHOR_NAME")
|
||||
self.assertIsNone(original_author_name)
|
||||
|
||||
# Test user commit with explicit no-committer attribution
|
||||
git_repo_user_no_committer = GitRepo(io, None, None, attribute_committer=False)
|
||||
fname.write_text("user no committer content")
|
||||
commit_result = git_repo_user_no_committer.commit(fnames=[str(fname)], aider_edits=False)
|
||||
self.assertIsNotNone(commit_result)
|
||||
commit = raw_repo.head.commit
|
||||
self.assertEqual(commit.author.name, "Test User", msg="Author name should not be modified for user commits")
|
||||
self.assertEqual(commit.committer.name, "Test User", msg="Committer name should not be modified when attribute_committer=False")
|
||||
|
||||
@unittest.skipIf(platform.system() == "Windows", "Git env var behavior differs on Windows")
|
||||
def test_commit_with_co_authored_by(self):
|
||||
with GitTemporaryDirectory():
|
||||
# new repo
|
||||
raw_repo = git.Repo()
|
||||
raw_repo.config_writer().set_value("user", "name", "Test User").release()
|
||||
raw_repo.config_writer().set_value("user", "email", "test@example.com").release()
|
||||
|
||||
# add a file and commit it
|
||||
fname = Path("file.txt")
|
||||
fname.touch()
|
||||
raw_repo.git.add(str(fname))
|
||||
raw_repo.git.commit("-m", "initial commit")
|
||||
|
||||
# Mock coder args: Co-authored-by enabled, author/committer use default (None)
|
||||
mock_coder = MagicMock()
|
||||
mock_coder.args.attribute_co_authored_by = True
|
||||
mock_coder.args.attribute_author = None # Default
|
||||
mock_coder.args.attribute_committer = None # Default
|
||||
mock_coder.args.attribute_commit_message_author = False
|
||||
mock_coder.args.attribute_commit_message_committer = False
|
||||
# The code uses coder.main_model.name for the co-authored-by line
|
||||
mock_coder.main_model = MagicMock()
|
||||
mock_coder.main_model.name = "gpt-test"
|
||||
|
||||
|
||||
io = InputOutput()
|
||||
git_repo = GitRepo(io, None, None)
|
||||
|
||||
# commit a change with aider_edits=True and co-authored-by flag
|
||||
fname.write_text("new content")
|
||||
commit_result = git_repo.commit(fnames=[str(fname)], aider_edits=True, coder=mock_coder, message="Aider edit")
|
||||
self.assertIsNotNone(commit_result)
|
||||
|
||||
# check the commit message and author/committer
|
||||
commit = raw_repo.head.commit
|
||||
self.assertIn("Co-authored-by: aider (gpt-test) <noreply@aider.chat>", commit.message)
|
||||
self.assertEqual(commit.message.splitlines()[0], "Aider edit")
|
||||
# With default (None), co-authored-by takes precedence
|
||||
self.assertEqual(commit.author.name, "Test User", msg="Author name should not be modified when co-authored-by takes precedence")
|
||||
self.assertEqual(commit.committer.name, "Test User", msg="Committer name should not be modified when co-authored-by takes precedence")
|
||||
|
||||
@unittest.skipIf(platform.system() == "Windows", "Git env var behavior differs on Windows")
|
||||
def test_commit_co_authored_by_with_explicit_name_modification(self):
|
||||
# Test scenario where Co-authored-by is true AND author/committer modification are explicitly True
|
||||
with GitTemporaryDirectory():
|
||||
# Setup repo...
|
||||
# new repo
|
||||
raw_repo = git.Repo()
|
||||
raw_repo.config_writer().set_value("user", "name", "Test User").release()
|
||||
raw_repo.config_writer().set_value("user", "email", "test@example.com").release()
|
||||
|
||||
# add a file and commit it
|
||||
fname = Path("file.txt")
|
||||
fname.touch()
|
||||
raw_repo.git.add(str(fname))
|
||||
raw_repo.git.commit("-m", "initial commit")
|
||||
|
||||
# Mock coder args: Co-authored-by enabled, author/committer modification explicitly enabled
|
||||
mock_coder = MagicMock()
|
||||
mock_coder.args.attribute_co_authored_by = True
|
||||
mock_coder.args.attribute_author = True # Explicitly enable
|
||||
mock_coder.args.attribute_committer = True # Explicitly enable
|
||||
mock_coder.args.attribute_commit_message_author = False
|
||||
mock_coder.args.attribute_commit_message_committer = False
|
||||
mock_coder.main_model = MagicMock()
|
||||
mock_coder.main_model.name = "gpt-test-combo"
|
||||
|
||||
|
||||
io = InputOutput()
|
||||
git_repo = GitRepo(io, None, None)
|
||||
|
||||
# commit a change with aider_edits=True and combo flags
|
||||
fname.write_text("new content combo")
|
||||
commit_result = git_repo.commit(fnames=[str(fname)], aider_edits=True, coder=mock_coder, message="Aider combo edit")
|
||||
self.assertIsNotNone(commit_result)
|
||||
|
||||
# check the commit message and author/committer
|
||||
commit = raw_repo.head.commit
|
||||
self.assertIn("Co-authored-by: aider (gpt-test-combo) <noreply@aider.chat>", commit.message)
|
||||
self.assertEqual(commit.message.splitlines()[0], "Aider combo edit")
|
||||
# When co-authored-by is true BUT author/committer are explicit True, modification SHOULD happen
|
||||
self.assertEqual(commit.author.name, "Test User (aider)", msg="Author name should be modified when explicitly True, even with co-author")
|
||||
self.assertEqual(commit.committer.name, "Test User (aider)", msg="Committer name should be modified when explicitly True, even with co-author")
|
||||
|
||||
@unittest.skipIf(platform.system() == "Windows", "Git env var behavior differs on Windows")
|
||||
def test_commit_ai_edits_no_coauthor_explicit_false(self):
|
||||
# Test AI edits (aider_edits=True) when co-authored-by is False,
|
||||
# but author or committer attribution is explicitly disabled.
|
||||
with GitTemporaryDirectory():
|
||||
# Setup repo
|
||||
raw_repo = git.Repo()
|
||||
raw_repo.config_writer().set_value("user", "name", "Test User").release()
|
||||
raw_repo.config_writer().set_value("user", "email", "test@example.com").release()
|
||||
fname = Path("file.txt")
|
||||
fname.touch()
|
||||
raw_repo.git.add(str(fname))
|
||||
raw_repo.git.commit("-m", "initial commit")
|
||||
|
||||
io = InputOutput()
|
||||
|
||||
# Case 1: attribute_author = False, attribute_committer = None (default True)
|
||||
mock_coder_no_author = MagicMock()
|
||||
mock_coder_no_author.args.attribute_co_authored_by = False
|
||||
mock_coder_no_author.args.attribute_author = False # Explicit False
|
||||
mock_coder_no_author.args.attribute_committer = None # Default True
|
||||
mock_coder_no_author.args.attribute_commit_message_author = False
|
||||
mock_coder_no_author.args.attribute_commit_message_committer = False
|
||||
mock_coder_no_author.main_model = MagicMock()
|
||||
mock_coder_no_author.main_model.name = "gpt-test-no-author"
|
||||
|
||||
git_repo_no_author = GitRepo(io, None, None)
|
||||
fname.write_text("no author content")
|
||||
commit_result = git_repo_no_author.commit(fnames=[str(fname)], aider_edits=True, coder=mock_coder_no_author, message="Aider no author")
|
||||
self.assertIsNotNone(commit_result)
|
||||
commit = raw_repo.head.commit
|
||||
self.assertNotIn("Co-authored-by:", commit.message)
|
||||
self.assertEqual(commit.author.name, "Test User") # Explicit False
|
||||
self.assertEqual(commit.committer.name, "Test User (aider)") # Default True
|
||||
|
||||
# Case 2: attribute_author = None (default True), attribute_committer = False
|
||||
mock_coder_no_committer = MagicMock()
|
||||
mock_coder_no_committer.args.attribute_co_authored_by = False
|
||||
mock_coder_no_committer.args.attribute_author = None # Default True
|
||||
mock_coder_no_committer.args.attribute_committer = False # Explicit False
|
||||
mock_coder_no_committer.args.attribute_commit_message_author = False
|
||||
mock_coder_no_committer.args.attribute_commit_message_committer = False
|
||||
mock_coder_no_committer.main_model = MagicMock()
|
||||
mock_coder_no_committer.main_model.name = "gpt-test-no-committer"
|
||||
|
||||
git_repo_no_committer = GitRepo(io, None, None)
|
||||
fname.write_text("no committer content")
|
||||
commit_result = git_repo_no_committer.commit(fnames=[str(fname)], aider_edits=True, coder=mock_coder_no_committer, message="Aider no committer")
|
||||
self.assertIsNotNone(commit_result)
|
||||
commit = raw_repo.head.commit
|
||||
self.assertNotIn("Co-authored-by:", commit.message)
|
||||
self.assertEqual(commit.author.name, "Test User (aider)", msg="Author name should be modified (default True) when co-author=False")
|
||||
self.assertEqual(commit.committer.name, "Test User", msg="Committer name should not be modified (explicit False) when co-author=False")
|
||||
|
||||
def test_get_tracked_files(self):
|
||||
# Create a temporary directory
|
||||
tempdir = Path(tempfile.mkdtemp())
|
||||
|
@ -404,14 +561,12 @@ class TestRepo(unittest.TestCase):
|
|||
|
||||
git_repo = GitRepo(InputOutput(), None, None)
|
||||
|
||||
git_repo.commit(fnames=[str(fname)])
|
||||
commit_result = git_repo.commit(fnames=[str(fname)])
|
||||
self.assertIsNone(commit_result)
|
||||
|
||||
@unittest.skipIf(platform.system() == "Windows", "Git hook execution differs on Windows")
|
||||
def test_git_commit_verify(self):
|
||||
"""Test that git_commit_verify controls whether --no-verify is passed to git commit"""
|
||||
# Skip on Windows as hook execution works differently
|
||||
if platform.system() == "Windows":
|
||||
return
|
||||
|
||||
with GitTemporaryDirectory():
|
||||
# Create a new repo
|
||||
raw_repo = git.Repo()
|
||||
|
|
|
@ -314,8 +314,6 @@ class TestRepoMapAllLanguages(unittest.TestCase):
|
|||
def test_language_lua(self):
|
||||
self._test_language_repo_map("lua", "lua", "greet")
|
||||
|
||||
# "ocaml": ("ml", "Greeter"), # not supported in tsl-pack (yet?)
|
||||
|
||||
def test_language_php(self):
|
||||
self._test_language_repo_map("php", "php", "greet")
|
||||
|
||||
|
@ -384,6 +382,12 @@ class TestRepoMapAllLanguages(unittest.TestCase):
|
|||
def test_language_scala(self):
|
||||
self._test_language_repo_map("scala", "scala", "Greeter")
|
||||
|
||||
def test_language_ocaml(self):
|
||||
self._test_language_repo_map("ocaml", "ml", "Greeter")
|
||||
|
||||
def test_language_ocaml_interface(self):
|
||||
self._test_language_repo_map("ocaml_interface", "mli", "Greeter")
|
||||
|
||||
def _test_language_repo_map(self, lang, key, symbol):
|
||||
"""Helper method to test repo map generation for a specific language."""
|
||||
# Get the fixture file path and name based on language
|
||||
|
@ -407,6 +411,7 @@ class TestRepoMapAllLanguages(unittest.TestCase):
|
|||
dump(lang)
|
||||
dump(result)
|
||||
|
||||
print(result)
|
||||
self.assertGreater(len(result.strip().splitlines()), 1)
|
||||
|
||||
# Check if the result contains all the expected files and symbols
|
||||
|
|
14
tests/fixtures/languages/ocaml_interface/test.mli
vendored
Normal file
14
tests/fixtures/languages/ocaml_interface/test.mli
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
(* Module definition *)
|
||||
module Greeter : sig
|
||||
type person = {
|
||||
name: string;
|
||||
age: int
|
||||
}
|
||||
|
||||
val create_person : string -> int -> person
|
||||
|
||||
val greet : person -> unit
|
||||
end
|
||||
|
||||
(* Outside the module *)
|
||||
val main : unit -> unit
|
120
tests/scrape/test_playwright_disable.py
Normal file
120
tests/scrape/test_playwright_disable.py
Normal file
|
@ -0,0 +1,120 @@
|
|||
import pytest
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from aider.scrape import install_playwright, Scraper
|
||||
|
||||
class DummyIO:
|
||||
def __init__(self):
|
||||
self.outputs = []
|
||||
self.confirmed = False
|
||||
|
||||
def tool_output(self, msg):
|
||||
self.outputs.append(msg)
|
||||
|
||||
def confirm_ask(self, msg, default="y"):
|
||||
self.outputs.append(f"confirm: {msg}")
|
||||
return self.confirmed
|
||||
|
||||
def tool_error(self, msg):
|
||||
self.outputs.append(f"error: {msg}")
|
||||
|
||||
|
||||
def test_scraper_disable_playwright_flag(monkeypatch):
|
||||
io = DummyIO()
|
||||
# Simulate that playwright is not available (disable_playwright just means playwright_available=False)
|
||||
scraper = Scraper(print_error=io.tool_error, playwright_available=False)
|
||||
# Patch scrape_with_httpx to check it is called
|
||||
called = {}
|
||||
def fake_httpx(url):
|
||||
called['called'] = True
|
||||
return "plain text", "text/plain"
|
||||
scraper.scrape_with_httpx = fake_httpx
|
||||
content = scraper.scrape("http://example.com")
|
||||
assert content == "plain text"
|
||||
assert called['called']
|
||||
|
||||
def test_scraper_enable_playwright(monkeypatch):
|
||||
io = DummyIO()
|
||||
# Simulate that playwright is available and should be used
|
||||
scraper = Scraper(print_error=io.tool_error, playwright_available=True)
|
||||
# Patch scrape_with_playwright to check it is called
|
||||
called = {}
|
||||
def fake_playwright(url):
|
||||
called['called'] = True
|
||||
return "<html>hi</html>", "text/html"
|
||||
scraper.scrape_with_playwright = fake_playwright
|
||||
content = scraper.scrape("http://example.com")
|
||||
assert content.startswith("hi") or "<html>" in content
|
||||
assert called['called']
|
||||
|
||||
def test_commands_web_disable_playwright(monkeypatch):
|
||||
"""
|
||||
Test that Commands.cmd_web does not emit a misleading warning when --disable-playwright is set.
|
||||
"""
|
||||
from aider.commands import Commands
|
||||
|
||||
# Dummy IO to capture outputs and warnings
|
||||
class DummyIO:
|
||||
def __init__(self):
|
||||
self.outputs = []
|
||||
self.warnings = []
|
||||
self.errors = []
|
||||
def tool_output(self, msg, *a, **k):
|
||||
self.outputs.append(msg)
|
||||
def tool_warning(self, msg, *a, **k):
|
||||
self.warnings.append(msg)
|
||||
def tool_error(self, msg, *a, **k):
|
||||
self.errors.append(msg)
|
||||
def read_text(self, filename, silent=False):
|
||||
return ""
|
||||
def confirm_ask(self, *a, **k):
|
||||
return True
|
||||
def print(self, *a, **k):
|
||||
pass
|
||||
|
||||
# Dummy coder to satisfy Commands
|
||||
class DummyCoder:
|
||||
def __init__(self):
|
||||
self.cur_messages = []
|
||||
self.main_model = type("M", (), {"edit_format": "code", "name": "dummy", "info": {}})
|
||||
def get_rel_fname(self, fname):
|
||||
return fname
|
||||
def get_inchat_relative_files(self):
|
||||
return []
|
||||
def abs_root_path(self, fname):
|
||||
return fname
|
||||
def get_all_abs_files(self):
|
||||
return []
|
||||
def get_announcements(self):
|
||||
return []
|
||||
def format_chat_chunks(self):
|
||||
return type("Chunks", (), {"repo": [], "readonly_files": [], "chat_files": []})()
|
||||
def event(self, *a, **k):
|
||||
pass
|
||||
|
||||
# Patch install_playwright to always return False (simulate not available)
|
||||
monkeypatch.setattr("aider.scrape.install_playwright", lambda io: False)
|
||||
|
||||
# Patch Scraper to always use scrape_with_httpx and never warn
|
||||
class DummyScraper:
|
||||
def __init__(self, **kwargs):
|
||||
self.called = False
|
||||
def scrape(self, url):
|
||||
self.called = True
|
||||
return "dummy content"
|
||||
|
||||
monkeypatch.setattr("aider.commands.Scraper", DummyScraper)
|
||||
|
||||
io = DummyIO()
|
||||
coder = DummyCoder()
|
||||
args = type("Args", (), {"disable_playwright": True})()
|
||||
commands = Commands(io, coder, args=args)
|
||||
|
||||
commands.cmd_web("http://example.com")
|
||||
# Should not emit a warning about playwright
|
||||
assert not io.warnings
|
||||
# Should not contain message "For the best web scraping, install Playwright:"
|
||||
assert all("install Playwright:" not in msg for msg in io.outputs)
|
||||
# Should output scraping and added to chat
|
||||
assert any("Scraping" in msg for msg in io.outputs)
|
||||
assert any("added to chat" in msg for msg in io.outputs)
|
Loading…
Add table
Add a link
Reference in a new issue