mirror of
https://github.com/Aider-AI/aider.git
synced 2025-05-30 17:24:59 +00:00
Merge branch 'main' into json-coders
This commit is contained in:
commit
e1b83ba6b5
81 changed files with 2210 additions and 720 deletions
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
|
@ -21,12 +21,12 @@ jobs:
|
|||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install setuptools wheel twine importlib-metadata==7.2.1
|
||||
pip install build setuptools wheel twine importlib-metadata==7.2.1
|
||||
|
||||
- name: Build and publish
|
||||
env:
|
||||
TWINE_USERNAME: __token__
|
||||
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
|
||||
run: |
|
||||
python setup.py sdist bdist_wheel
|
||||
python -m build
|
||||
twine upload dist/*
|
||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -5,6 +5,7 @@ aider.code-workspace
|
|||
.aider*
|
||||
aider_chat.egg-info/
|
||||
build
|
||||
dist/
|
||||
Gemfile.lock
|
||||
_site
|
||||
.jekyll-cache/
|
||||
|
|
45
HISTORY.md
45
HISTORY.md
|
@ -1,6 +1,51 @@
|
|||
|
||||
# Release history
|
||||
|
||||
### Aider v0.50.0
|
||||
|
||||
- Infinite output for DeepSeek Coder, Mistral models in addition to Anthropic's models.
|
||||
- New `--deepseek` switch to use DeepSeek Coder.
|
||||
- DeepSeek Coder uses 8k token output.
|
||||
- New `--chat-mode <mode>` switch to launch in ask/help/code modes.
|
||||
- New `/code <message>` command request a code edit while in `ask` mode.
|
||||
- Web scraper is more robust if page never idles.
|
||||
- Improved token and cost reporting for infinite output.
|
||||
- Improvements and bug fixes for `/read` only files.
|
||||
- Switched from `setup.py` to `pyproject.toml`, by @branchvincent.
|
||||
- Bug fix to persist files added during `/ask`.
|
||||
- Bug fix for chat history size in `/tokens`.
|
||||
- Aider wrote 66% of the code in this release.
|
||||
|
||||
### Aider v0.49.1
|
||||
|
||||
- Bugfix to `/help`.
|
||||
|
||||
### Aider v0.49.0
|
||||
|
||||
- Add read-only files to the chat context with `/read` and `--read`, including from outside the git repo.
|
||||
- `/diff` now shows diffs of all changes resulting from your request, including lint and test fixes.
|
||||
- New `/clipboard` command to paste images or text from the clipboard, replaces `/add-clipboard-image`.
|
||||
- Now shows the markdown scraped when you add a url with `/web`.
|
||||
- When [scripting aider](https://aider.chat/docs/scripting.html) messages can now contain in-chat `/` commands.
|
||||
- Aider in docker image now suggests the correct command to update to latest version.
|
||||
- Improved retries on API errors (was easy to test during Sonnet outage).
|
||||
- Added `--mini` for `gpt-4o-mini`.
|
||||
- Bugfix to keep session cost accurate when using `/ask` and `/help`.
|
||||
- Performance improvements for repo map calculation.
|
||||
- `/tokens` now shows the active model.
|
||||
- Enhanced commit message attribution options:
|
||||
- New `--attribute-commit-message-author` to prefix commit messages with 'aider: ' if aider authored the changes, replaces `--attribute-commit-message`.
|
||||
- New `--attribute-commit-message-committer` to prefix all commit messages with 'aider: '.
|
||||
- Aider wrote 61% of the code in this release.
|
||||
|
||||
### Aider v0.48.1
|
||||
|
||||
- Added `openai/gpt-4o-2024-08-06`.
|
||||
- Worked around litellm bug that removes OpenRouter app headers when using `extra_headers`.
|
||||
- Improved progress indication during repo map processing.
|
||||
- Corrected instructions for upgrading the docker container to latest aider version.
|
||||
- Removed obsolete 16k token limit on commit diffs, use per-model limits.
|
||||
|
||||
### Aider v0.48.0
|
||||
|
||||
- Performance improvements for large/mono repos.
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
include requirements.txt
|
12
README.md
12
README.md
|
@ -35,18 +35,18 @@ cog.out(open("aider/website/_includes/get-started.md").read())
|
|||
You can get started quickly like this:
|
||||
|
||||
```
|
||||
$ pip install aider-chat
|
||||
python -m pip install aider-chat
|
||||
|
||||
# Change directory into a git repo
|
||||
$ cd /to/your/git/repo
|
||||
cd /to/your/git/repo
|
||||
|
||||
# Work with Claude 3.5 Sonnet on your repo
|
||||
$ export ANTHROPIC_API_KEY=your-key-goes-here
|
||||
$ aider
|
||||
export ANTHROPIC_API_KEY=your-key-goes-here
|
||||
aider
|
||||
|
||||
# Work with GPT-4o on your repo
|
||||
$ export OPENAI_API_KEY=your-key-goes-here
|
||||
$ aider
|
||||
export OPENAI_API_KEY=your-key-goes-here
|
||||
aider
|
||||
```
|
||||
<!--[[[end]]]-->
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
__version__ = "0.48.1-dev"
|
||||
__version__ = "0.50.1-dev"
|
||||
|
|
|
@ -82,6 +82,14 @@ def get_parser(default_config_files, git_root):
|
|||
const=gpt_4o_model,
|
||||
help=f"Use {gpt_4o_model} model for the main chat",
|
||||
)
|
||||
gpt_4o_mini_model = "gpt-4o-mini"
|
||||
group.add_argument(
|
||||
"--mini",
|
||||
action="store_const",
|
||||
dest="model",
|
||||
const=gpt_4o_mini_model,
|
||||
help=f"Use {gpt_4o_mini_model} model for the main chat",
|
||||
)
|
||||
gpt_4_turbo_model = "gpt-4-1106-preview"
|
||||
group.add_argument(
|
||||
"--4-turbo",
|
||||
|
@ -101,6 +109,14 @@ def get_parser(default_config_files, git_root):
|
|||
const=gpt_3_model_name,
|
||||
help=f"Use {gpt_3_model_name} model for the main chat",
|
||||
)
|
||||
deepseek_model = "deepseek/deepseek-coder"
|
||||
group.add_argument(
|
||||
"--deepseek",
|
||||
action="store_const",
|
||||
dest="model",
|
||||
const=deepseek_model,
|
||||
help=f"Use {deepseek_model} model for the main chat",
|
||||
)
|
||||
|
||||
##########
|
||||
group = parser.add_argument_group("Model Settings")
|
||||
|
@ -159,6 +175,7 @@ def get_parser(default_config_files, git_root):
|
|||
)
|
||||
group.add_argument(
|
||||
"--edit-format",
|
||||
"--chat-mode",
|
||||
metavar="EDIT_FORMAT",
|
||||
default=None,
|
||||
help="Specify what edit format the LLM should use (default depends on model)",
|
||||
|
@ -350,10 +367,16 @@ def get_parser(default_config_files, git_root):
|
|||
help="Attribute aider commits in the git committer name (default: True)",
|
||||
)
|
||||
group.add_argument(
|
||||
"--attribute-commit-message",
|
||||
"--attribute-commit-message-author",
|
||||
action=argparse.BooleanOptionalAction,
|
||||
default=False,
|
||||
help="Prefix commit messages with 'aider: ' (default: False)",
|
||||
help="Prefix commit messages with 'aider: ' if aider authored the changes (default: False)",
|
||||
)
|
||||
group.add_argument(
|
||||
"--attribute-commit-message-committer",
|
||||
action=argparse.BooleanOptionalAction,
|
||||
default=False,
|
||||
help="Prefix all commit messages with 'aider: ' (default: False)",
|
||||
)
|
||||
group.add_argument(
|
||||
"--commit",
|
||||
|
@ -420,6 +443,12 @@ def get_parser(default_config_files, git_root):
|
|||
metavar="FILE",
|
||||
help="specify a file to edit (can be used multiple times)",
|
||||
)
|
||||
group.add_argument(
|
||||
"--read",
|
||||
action="append",
|
||||
metavar="FILE",
|
||||
help="specify a read-only file (can be used multiple times)",
|
||||
)
|
||||
group.add_argument(
|
||||
"--vim",
|
||||
action="store_true",
|
||||
|
|
|
@ -30,7 +30,7 @@ from aider.llm import litellm
|
|||
from aider.mdstream import MarkdownStream
|
||||
from aider.repo import GitRepo
|
||||
from aider.repomap import RepoMap
|
||||
from aider.sendchat import send_with_retries
|
||||
from aider.sendchat import retry_exceptions, send_completion
|
||||
from aider.utils import format_content, format_messages, is_image_file
|
||||
|
||||
from ..dump import dump # noqa: F401
|
||||
|
@ -50,6 +50,7 @@ def wrap_fence(name):
|
|||
|
||||
class Coder:
|
||||
abs_fnames = None
|
||||
abs_read_only_fnames = None
|
||||
repo = None
|
||||
last_aider_commit_hash = None
|
||||
aider_edited_files = None
|
||||
|
@ -70,6 +71,11 @@ class Coder:
|
|||
lint_outcome = None
|
||||
test_outcome = None
|
||||
multi_response_content = ""
|
||||
partial_response_content = ""
|
||||
commit_before_message = []
|
||||
message_cost = 0.0
|
||||
message_tokens_sent = 0
|
||||
message_tokens_received = 0
|
||||
|
||||
@classmethod
|
||||
def create(
|
||||
|
@ -89,6 +95,8 @@ class Coder:
|
|||
else:
|
||||
main_model = models.Model(models.DEFAULT_MODEL_NAME)
|
||||
|
||||
if edit_format == "code":
|
||||
edit_format = None
|
||||
if edit_format is None:
|
||||
if from_coder:
|
||||
edit_format = from_coder.edit_format
|
||||
|
@ -112,6 +120,7 @@ class Coder:
|
|||
# Bring along context from the old Coder
|
||||
update = dict(
|
||||
fnames=list(from_coder.abs_fnames),
|
||||
read_only_fnames=list(from_coder.abs_read_only_fnames), # Copy read-only files
|
||||
done_messages=done_messages,
|
||||
cur_messages=from_coder.cur_messages,
|
||||
aider_commit_hashes=from_coder.aider_commit_hashes,
|
||||
|
@ -143,7 +152,10 @@ class Coder:
|
|||
main_model = self.main_model
|
||||
weak_model = main_model.weak_model
|
||||
prefix = "Model:"
|
||||
output = f" {main_model.name} with {self.edit_format} edit format"
|
||||
output = f" {main_model.name} with"
|
||||
if main_model.info.get("supports_assistant_prefill"):
|
||||
output += " ♾️"
|
||||
output += f" {self.edit_format} edit format"
|
||||
if weak_model is not main_model:
|
||||
prefix = "Models:"
|
||||
output += f", weak model {weak_model.name}"
|
||||
|
@ -193,7 +205,7 @@ class Coder:
|
|||
io,
|
||||
repo=None,
|
||||
fnames=None,
|
||||
pretty=True,
|
||||
read_only_fnames=None,
|
||||
show_diffs=False,
|
||||
auto_commits=True,
|
||||
dirty_commits=True,
|
||||
|
@ -217,6 +229,7 @@ class Coder:
|
|||
summarizer=None,
|
||||
total_cost=0.0,
|
||||
):
|
||||
self.commit_before_message = []
|
||||
self.aider_commit_hashes = set()
|
||||
self.rejected_urls = set()
|
||||
self.abs_root_path_cache = {}
|
||||
|
@ -240,6 +253,7 @@ class Coder:
|
|||
|
||||
self.verbose = verbose
|
||||
self.abs_fnames = set()
|
||||
self.abs_read_only_fnames = set()
|
||||
|
||||
if cur_messages:
|
||||
self.cur_messages = cur_messages
|
||||
|
@ -263,9 +277,9 @@ class Coder:
|
|||
self.code_theme = code_theme
|
||||
|
||||
self.dry_run = dry_run
|
||||
self.pretty = pretty
|
||||
self.pretty = self.io.pretty
|
||||
|
||||
if pretty:
|
||||
if self.pretty:
|
||||
self.console = Console()
|
||||
else:
|
||||
self.console = Console(force_terminal=False, no_color=True)
|
||||
|
@ -314,6 +328,15 @@ class Coder:
|
|||
if not self.repo:
|
||||
self.find_common_root()
|
||||
|
||||
if read_only_fnames:
|
||||
self.abs_read_only_fnames = set()
|
||||
for fname in read_only_fnames:
|
||||
abs_fname = self.abs_root_path(fname)
|
||||
if os.path.exists(abs_fname):
|
||||
self.abs_read_only_fnames.add(abs_fname)
|
||||
else:
|
||||
self.io.tool_error(f"Error: Read-only file {fname} does not exist. Skipping.")
|
||||
|
||||
if map_tokens is None:
|
||||
use_repo_map = main_model.use_repo_map
|
||||
map_tokens = 1024
|
||||
|
@ -376,8 +399,10 @@ class Coder:
|
|||
self.linter.set_linter(lang, cmd)
|
||||
|
||||
def show_announcements(self):
|
||||
bold = True
|
||||
for line in self.get_announcements():
|
||||
self.io.tool_output(line)
|
||||
self.io.tool_output(line, bold=bold)
|
||||
bold = False
|
||||
|
||||
def find_common_root(self):
|
||||
if len(self.abs_fnames) == 1:
|
||||
|
@ -444,6 +469,10 @@ class Coder:
|
|||
all_content = ""
|
||||
for _fname, content in self.get_abs_fnames_content():
|
||||
all_content += content + "\n"
|
||||
for _fname in self.abs_read_only_fnames:
|
||||
content = self.io.read_text(_fname)
|
||||
if content is not None:
|
||||
all_content += content + "\n"
|
||||
|
||||
good = False
|
||||
for fence_open, fence_close in self.fences:
|
||||
|
@ -485,6 +514,19 @@ class Coder:
|
|||
|
||||
return prompt
|
||||
|
||||
def get_read_only_files_content(self):
|
||||
prompt = ""
|
||||
for fname in self.abs_read_only_fnames:
|
||||
content = self.io.read_text(fname)
|
||||
if content is not None and not is_image_file(fname):
|
||||
relative_fname = self.get_rel_fname(fname)
|
||||
prompt += "\n"
|
||||
prompt += relative_fname
|
||||
prompt += f"\n{self.fence[0]}\n"
|
||||
prompt += content
|
||||
prompt += f"{self.fence[1]}\n"
|
||||
return prompt
|
||||
|
||||
def get_cur_message_text(self):
|
||||
text = ""
|
||||
for msg in self.cur_messages:
|
||||
|
@ -522,9 +564,13 @@ class Coder:
|
|||
|
||||
mentioned_fnames.update(self.get_ident_filename_matches(mentioned_idents))
|
||||
|
||||
other_files = set(self.get_all_abs_files()) - set(self.abs_fnames)
|
||||
all_abs_files = set(self.get_all_abs_files())
|
||||
repo_abs_read_only_fnames = set(self.abs_read_only_fnames) & all_abs_files
|
||||
chat_files = set(self.abs_fnames) | repo_abs_read_only_fnames
|
||||
other_files = all_abs_files - chat_files
|
||||
|
||||
repo_content = self.repo_map.get_repo_map(
|
||||
self.abs_fnames,
|
||||
chat_files,
|
||||
other_files,
|
||||
mentioned_fnames=mentioned_fnames,
|
||||
mentioned_idents=mentioned_idents,
|
||||
|
@ -534,7 +580,7 @@ class Coder:
|
|||
if not repo_content:
|
||||
repo_content = self.repo_map.get_repo_map(
|
||||
set(),
|
||||
set(self.get_all_abs_files()),
|
||||
all_abs_files,
|
||||
mentioned_fnames=mentioned_fnames,
|
||||
mentioned_idents=mentioned_idents,
|
||||
)
|
||||
|
@ -543,7 +589,7 @@ class Coder:
|
|||
if not repo_content:
|
||||
repo_content = self.repo_map.get_repo_map(
|
||||
set(),
|
||||
set(self.get_all_abs_files()),
|
||||
all_abs_files,
|
||||
)
|
||||
|
||||
return repo_content
|
||||
|
@ -572,12 +618,6 @@ class Coder:
|
|||
files_content = self.gpt_prompts.files_no_full_files
|
||||
files_reply = "Ok."
|
||||
|
||||
if files_content:
|
||||
files_messages += [
|
||||
dict(role="user", content=files_content),
|
||||
dict(role="assistant", content=files_reply),
|
||||
]
|
||||
|
||||
images_message = self.get_images_message()
|
||||
if images_message is not None:
|
||||
files_messages += [
|
||||
|
@ -585,6 +625,24 @@ class Coder:
|
|||
dict(role="assistant", content="Ok."),
|
||||
]
|
||||
|
||||
read_only_content = self.get_read_only_files_content()
|
||||
if read_only_content:
|
||||
files_messages += [
|
||||
dict(
|
||||
role="user", content=self.gpt_prompts.read_only_files_prefix + read_only_content
|
||||
),
|
||||
dict(
|
||||
role="assistant",
|
||||
content="Ok, I will use these files as references.",
|
||||
),
|
||||
]
|
||||
|
||||
if files_content:
|
||||
files_messages += [
|
||||
dict(role="user", content=files_content),
|
||||
dict(role="assistant", content=files_reply),
|
||||
]
|
||||
|
||||
return files_messages
|
||||
|
||||
def get_images_message(self):
|
||||
|
@ -597,9 +655,11 @@ class Coder:
|
|||
mime_type, _ = mimetypes.guess_type(fname)
|
||||
if mime_type and mime_type.startswith("image/"):
|
||||
image_url = f"data:{mime_type};base64,{content}"
|
||||
image_messages.append(
|
||||
{"type": "image_url", "image_url": {"url": image_url, "detail": "high"}}
|
||||
)
|
||||
rel_fname = self.get_rel_fname(fname)
|
||||
image_messages += [
|
||||
{"type": "text", "text": f"Image file: {rel_fname}"},
|
||||
{"type": "image_url", "image_url": {"url": image_url, "detail": "high"}},
|
||||
]
|
||||
|
||||
if not image_messages:
|
||||
return None
|
||||
|
@ -609,7 +669,7 @@ class Coder:
|
|||
def run_stream(self, user_message):
|
||||
self.io.user_input(user_message)
|
||||
self.init_before_message()
|
||||
yield from self.send_new_user_message(user_message)
|
||||
yield from self.send_message(user_message)
|
||||
|
||||
def init_before_message(self):
|
||||
self.reflected_message = None
|
||||
|
@ -617,48 +677,39 @@ class Coder:
|
|||
self.lint_outcome = None
|
||||
self.test_outcome = None
|
||||
self.edit_outcome = None
|
||||
if self.repo:
|
||||
self.commit_before_message.append(self.repo.get_head())
|
||||
|
||||
def run(self, with_message=None):
|
||||
while True:
|
||||
self.init_before_message()
|
||||
|
||||
def run(self, with_message=None, preproc=True):
|
||||
try:
|
||||
if with_message:
|
||||
new_user_message = with_message
|
||||
self.io.user_input(with_message)
|
||||
else:
|
||||
new_user_message = self.run_loop()
|
||||
|
||||
while new_user_message:
|
||||
self.reflected_message = None
|
||||
list(self.send_new_user_message(new_user_message))
|
||||
|
||||
new_user_message = None
|
||||
if self.reflected_message:
|
||||
if self.num_reflections < self.max_reflections:
|
||||
self.num_reflections += 1
|
||||
new_user_message = self.reflected_message
|
||||
else:
|
||||
self.io.tool_error(
|
||||
f"Only {self.max_reflections} reflections allowed, stopping."
|
||||
)
|
||||
|
||||
if with_message:
|
||||
self.run_one(with_message, preproc)
|
||||
return self.partial_response_content
|
||||
|
||||
while True:
|
||||
try:
|
||||
user_message = self.get_input()
|
||||
self.run_one(user_message, preproc)
|
||||
self.show_undo_hint()
|
||||
except KeyboardInterrupt:
|
||||
self.keyboard_interrupt()
|
||||
except EOFError:
|
||||
return
|
||||
|
||||
def run_loop(self):
|
||||
inp = self.io.get_input(
|
||||
def get_input(self):
|
||||
inchat_files = self.get_inchat_relative_files()
|
||||
read_only_files = [self.get_rel_fname(fname) for fname in self.abs_read_only_fnames]
|
||||
all_files = sorted(set(inchat_files + read_only_files))
|
||||
return self.io.get_input(
|
||||
self.root,
|
||||
self.get_inchat_relative_files(),
|
||||
all_files,
|
||||
self.get_addable_relative_files(),
|
||||
self.commands,
|
||||
self.abs_read_only_fnames,
|
||||
)
|
||||
|
||||
def preproc_user_input(self, inp):
|
||||
if not inp:
|
||||
return
|
||||
|
||||
|
@ -670,6 +721,28 @@ class Coder:
|
|||
|
||||
return inp
|
||||
|
||||
def run_one(self, user_message, preproc):
|
||||
self.init_before_message()
|
||||
|
||||
if preproc:
|
||||
message = self.preproc_user_input(user_message)
|
||||
else:
|
||||
message = user_message
|
||||
|
||||
while message:
|
||||
self.reflected_message = None
|
||||
list(self.send_message(message))
|
||||
|
||||
if not self.reflected_message:
|
||||
break
|
||||
|
||||
if self.num_reflections >= self.max_reflections:
|
||||
self.io.tool_error(f"Only {self.max_reflections} reflections allowed, stopping.")
|
||||
return
|
||||
|
||||
self.num_reflections += 1
|
||||
message = self.reflected_message
|
||||
|
||||
def check_for_urls(self, inp):
|
||||
url_pattern = re.compile(r"(https?://[^\s/$.?#].[^\s]*[^\s,.])")
|
||||
urls = list(set(url_pattern.findall(inp))) # Use set to remove duplicates
|
||||
|
@ -678,7 +751,7 @@ class Coder:
|
|||
if url not in self.rejected_urls:
|
||||
if self.io.confirm_ask(f"Add {url} to the chat?"):
|
||||
inp += "\n\n"
|
||||
inp += self.commands.cmd_web(url)
|
||||
inp += self.commands.cmd_web(url, paginate=False)
|
||||
added_urls.append(url)
|
||||
else:
|
||||
self.rejected_urls.add(url)
|
||||
|
@ -826,6 +899,7 @@ class Coder:
|
|||
|
||||
self.summarize_end()
|
||||
messages += self.done_messages
|
||||
|
||||
messages += self.get_files_messages()
|
||||
|
||||
if self.gpt_prompts.system_reminder:
|
||||
|
@ -852,7 +926,7 @@ class Coder:
|
|||
|
||||
final = messages[-1]
|
||||
|
||||
max_input_tokens = self.main_model.info.get("max_input_tokens")
|
||||
max_input_tokens = self.main_model.info.get("max_input_tokens") or 0
|
||||
# Add the reminder prompt if we still have room to include it.
|
||||
if (
|
||||
max_input_tokens is None
|
||||
|
@ -872,7 +946,7 @@ class Coder:
|
|||
|
||||
return messages
|
||||
|
||||
def send_new_user_message(self, inp):
|
||||
def send_message(self, inp):
|
||||
self.aider_edited_files = None
|
||||
|
||||
self.cur_messages += [
|
||||
|
@ -891,6 +965,8 @@ class Coder:
|
|||
else:
|
||||
self.mdstream = None
|
||||
|
||||
retry_delay = 0.125
|
||||
|
||||
self.usage_report = None
|
||||
exhausted = False
|
||||
interrupted = False
|
||||
|
@ -899,6 +975,14 @@ class Coder:
|
|||
try:
|
||||
yield from self.send(messages, functions=self.functions)
|
||||
break
|
||||
except retry_exceptions() as err:
|
||||
self.io.tool_error(str(err))
|
||||
retry_delay *= 2
|
||||
if retry_delay > 60:
|
||||
break
|
||||
self.io.tool_output(f"Retrying in {retry_delay:.1f} seconds...")
|
||||
time.sleep(retry_delay)
|
||||
continue
|
||||
except KeyboardInterrupt:
|
||||
interrupted = True
|
||||
break
|
||||
|
@ -911,7 +995,7 @@ class Coder:
|
|||
return
|
||||
except FinishReasonLength:
|
||||
# We hit the output limit!
|
||||
if not self.main_model.can_prefill:
|
||||
if not self.main_model.info.get("supports_assistant_prefill"):
|
||||
exhausted = True
|
||||
break
|
||||
|
||||
|
@ -920,7 +1004,9 @@ class Coder:
|
|||
if messages[-1]["role"] == "assistant":
|
||||
messages[-1]["content"] = self.multi_response_content
|
||||
else:
|
||||
messages.append(dict(role="assistant", content=self.multi_response_content))
|
||||
messages.append(
|
||||
dict(role="assistant", content=self.multi_response_content, prefix=True)
|
||||
)
|
||||
except Exception as err:
|
||||
self.io.tool_error(f"Unexpected error: {err}")
|
||||
traceback.print_exc()
|
||||
|
@ -935,8 +1021,7 @@ class Coder:
|
|||
|
||||
self.io.tool_output()
|
||||
|
||||
if self.usage_report:
|
||||
self.io.tool_output(self.usage_report)
|
||||
self.show_usage_report()
|
||||
|
||||
if exhausted:
|
||||
self.show_exhausted_error()
|
||||
|
@ -1011,10 +1096,10 @@ class Coder:
|
|||
output_tokens = 0
|
||||
if self.partial_response_content:
|
||||
output_tokens = self.main_model.token_count(self.partial_response_content)
|
||||
max_output_tokens = self.main_model.info.get("max_output_tokens", 0)
|
||||
max_output_tokens = self.main_model.info.get("max_output_tokens") or 0
|
||||
|
||||
input_tokens = self.main_model.token_count(self.format_messages())
|
||||
max_input_tokens = self.main_model.info.get("max_input_tokens", 0)
|
||||
max_input_tokens = self.main_model.info.get("max_input_tokens") or 0
|
||||
|
||||
total_tokens = input_tokens + output_tokens
|
||||
|
||||
|
@ -1159,9 +1244,8 @@ class Coder:
|
|||
|
||||
self.io.log_llm_history("TO LLM", format_messages(messages))
|
||||
|
||||
interrupted = False
|
||||
try:
|
||||
hash_object, completion = send_with_retries(
|
||||
hash_object, completion = send_completion(
|
||||
model.name,
|
||||
messages,
|
||||
functions,
|
||||
|
@ -1176,9 +1260,9 @@ class Coder:
|
|||
yield from self.show_send_output_stream(completion)
|
||||
else:
|
||||
self.show_send_output(completion)
|
||||
except KeyboardInterrupt:
|
||||
except KeyboardInterrupt as kbi:
|
||||
self.keyboard_interrupt()
|
||||
interrupted = True
|
||||
raise kbi
|
||||
finally:
|
||||
self.io.log_llm_history(
|
||||
"LLM RESPONSE",
|
||||
|
@ -1193,9 +1277,6 @@ class Coder:
|
|||
if args:
|
||||
self.io.ai_output(json.dumps(args, indent=4))
|
||||
|
||||
if interrupted:
|
||||
raise KeyboardInterrupt
|
||||
|
||||
self.calculate_and_show_tokens_and_cost(messages, completion)
|
||||
|
||||
def show_send_output(self, completion):
|
||||
|
@ -1218,7 +1299,7 @@ class Coder:
|
|||
show_func_err = func_err
|
||||
|
||||
try:
|
||||
self.partial_response_content = completion.choices[0].message.content
|
||||
self.partial_response_content = completion.choices[0].message.content or ""
|
||||
except AttributeError as content_err:
|
||||
show_content_err = content_err
|
||||
|
||||
|
@ -1312,13 +1393,19 @@ class Coder:
|
|||
prompt_tokens = self.main_model.token_count(messages)
|
||||
completion_tokens = self.main_model.token_count(self.partial_response_content)
|
||||
|
||||
self.usage_report = f"Tokens: {prompt_tokens:,} sent, {completion_tokens:,} received."
|
||||
self.message_tokens_sent += prompt_tokens
|
||||
self.message_tokens_received += completion_tokens
|
||||
|
||||
tokens_report = (
|
||||
f"Tokens: {self.message_tokens_sent:,} sent, {self.message_tokens_received:,} received."
|
||||
)
|
||||
|
||||
if self.main_model.info.get("input_cost_per_token"):
|
||||
cost += prompt_tokens * self.main_model.info.get("input_cost_per_token")
|
||||
if self.main_model.info.get("output_cost_per_token"):
|
||||
cost += completion_tokens * self.main_model.info.get("output_cost_per_token")
|
||||
self.total_cost += cost
|
||||
self.message_cost += cost
|
||||
|
||||
def format_cost(value):
|
||||
if value == 0:
|
||||
|
@ -1329,13 +1416,24 @@ class Coder:
|
|||
else:
|
||||
return f"{value:.{max(2, 2 - int(math.log10(magnitude)))}f}"
|
||||
|
||||
self.usage_report += (
|
||||
f" Cost: ${format_cost(cost)} request, ${format_cost(self.total_cost)} session."
|
||||
cost_report = (
|
||||
f" Cost: ${format_cost(self.message_cost)} message,"
|
||||
f" ${format_cost(self.total_cost)} session."
|
||||
)
|
||||
self.usage_report = tokens_report + cost_report
|
||||
else:
|
||||
self.usage_report = tokens_report
|
||||
|
||||
def show_usage_report(self):
|
||||
if self.usage_report:
|
||||
self.io.tool_output(self.usage_report)
|
||||
self.message_cost = 0.0
|
||||
self.message_tokens_sent = 0
|
||||
self.message_tokens_received = 0
|
||||
|
||||
def get_multi_response_content(self, final=False):
|
||||
cur = self.multi_response_content
|
||||
new = self.partial_response_content
|
||||
cur = self.multi_response_content or ""
|
||||
new = self.partial_response_content or ""
|
||||
|
||||
if new.rstrip() != new and not final:
|
||||
new = new.rstrip()
|
||||
|
@ -1377,7 +1475,10 @@ class Coder:
|
|||
return max(path.stat().st_mtime for path in files)
|
||||
|
||||
def get_addable_relative_files(self):
|
||||
return set(self.get_all_relative_files()) - set(self.get_inchat_relative_files())
|
||||
all_files = set(self.get_all_relative_files())
|
||||
inchat_files = set(self.get_inchat_relative_files())
|
||||
read_only_files = set(self.get_rel_fname(fname) for fname in self.abs_read_only_fnames)
|
||||
return all_files - inchat_files - read_only_files
|
||||
|
||||
def check_for_dirty_commit(self, path):
|
||||
if not self.repo:
|
||||
|
@ -1590,7 +1691,11 @@ class Coder:
|
|||
if self.show_diffs:
|
||||
self.commands.cmd_diff()
|
||||
|
||||
self.io.tool_output(f"You can use /undo to revert and discard commit {commit_hash}.")
|
||||
def show_undo_hint(self):
|
||||
if not self.commit_before_message:
|
||||
return
|
||||
if self.commit_before_message[-1] != self.repo.get_head():
|
||||
self.io.tool_output("You can use /undo to undo and discard each aider commit.")
|
||||
|
||||
def dirty_commit(self):
|
||||
if not self.need_commit_before_edits:
|
||||
|
|
|
@ -18,7 +18,7 @@ You always COMPLETELY IMPLEMENT the needed code!
|
|||
|
||||
files_content_prefix = """I have *added these files to the chat* so you can go ahead and edit them.
|
||||
|
||||
*Trust this message as the true contents of the files!*
|
||||
*Trust this message as the true contents of these files!*
|
||||
Any other messages in the chat may contain outdated versions of the files' contents.
|
||||
""" # noqa: E501
|
||||
|
||||
|
@ -38,4 +38,8 @@ Don't include files that might contain relevant context, just files that will ne
|
|||
repo_content_prefix = """Here are summaries of some files present in my git repository.
|
||||
Do not propose changes to these files, treat them as *read-only*.
|
||||
If you need to edit any of these files, ask me to *add them to the chat* first.
|
||||
"""
|
||||
|
||||
read_only_files_prefix = """Here are some READ ONLY files, provided for your reference.
|
||||
Do not edit these files!
|
||||
"""
|
||||
|
|
|
@ -7,7 +7,9 @@ from collections import OrderedDict
|
|||
from pathlib import Path
|
||||
|
||||
import git
|
||||
from PIL import ImageGrab
|
||||
import pyperclip
|
||||
from PIL import Image, ImageGrab
|
||||
from rich.text import Text
|
||||
|
||||
from aider import models, prompts, voice
|
||||
from aider.help import Help, install_help_extra
|
||||
|
@ -117,13 +119,15 @@ class Commands:
|
|||
else:
|
||||
self.io.tool_output("Please provide a partial model name to search for.")
|
||||
|
||||
def cmd_web(self, args):
|
||||
"Use headless selenium to scrape a webpage and add the content to the chat"
|
||||
def cmd_web(self, args, paginate=True):
|
||||
"Scrape a webpage, convert to markdown and add to the chat"
|
||||
|
||||
url = args.strip()
|
||||
if not url:
|
||||
self.io.tool_error("Please provide a URL to scrape.")
|
||||
return
|
||||
|
||||
self.io.tool_output(f"Scraping {url}...")
|
||||
if not self.scraper:
|
||||
res = install_playwright(self.io)
|
||||
if not res:
|
||||
|
@ -134,11 +138,14 @@ class Commands:
|
|||
)
|
||||
|
||||
content = self.scraper.scrape(url) or ""
|
||||
# if content:
|
||||
# self.io.tool_output(content)
|
||||
|
||||
content = f"{url}:\n\n" + content
|
||||
|
||||
self.io.tool_output("... done.")
|
||||
|
||||
if paginate:
|
||||
with self.io.console.pager():
|
||||
self.io.console.print(Text(content))
|
||||
|
||||
return content
|
||||
|
||||
def is_command(self, inp):
|
||||
|
@ -304,7 +311,6 @@ class Commands:
|
|||
# chat history
|
||||
msgs = self.coder.done_messages + self.coder.cur_messages
|
||||
if msgs:
|
||||
msgs = [dict(role="dummy", content=msg) for msg in msgs]
|
||||
tokens = self.coder.main_model.token_count(msgs)
|
||||
res.append((tokens, "chat history", "use /clear to clear"))
|
||||
|
||||
|
@ -316,6 +322,8 @@ class Commands:
|
|||
tokens = self.coder.main_model.token_count(repo_content)
|
||||
res.append((tokens, "repository map", "use --map-tokens to resize"))
|
||||
|
||||
fence = "`" * 3
|
||||
|
||||
# files
|
||||
for fname in self.coder.abs_fnames:
|
||||
relative_fname = self.coder.get_rel_fname(fname)
|
||||
|
@ -324,11 +332,23 @@ class Commands:
|
|||
tokens = self.coder.main_model.token_count_for_image(fname)
|
||||
else:
|
||||
# approximate
|
||||
content = f"{relative_fname}\n```\n" + content + "```\n"
|
||||
content = f"{relative_fname}\n{fence}\n" + content + "{fence}\n"
|
||||
tokens = self.coder.main_model.token_count(content)
|
||||
res.append((tokens, f"{relative_fname}", "use /drop to drop from chat"))
|
||||
res.append((tokens, f"{relative_fname}", "/drop to remove"))
|
||||
|
||||
self.io.tool_output("Approximate context window usage, in tokens:")
|
||||
# read-only files
|
||||
for fname in self.coder.abs_read_only_fnames:
|
||||
relative_fname = self.coder.get_rel_fname(fname)
|
||||
content = self.io.read_text(fname)
|
||||
if content is not None and not is_image_file(relative_fname):
|
||||
# approximate
|
||||
content = f"{relative_fname}\n{fence}\n" + content + "{fence}\n"
|
||||
tokens = self.coder.main_model.token_count(content)
|
||||
res.append((tokens, f"{relative_fname} (read-only)", "/drop to remove"))
|
||||
|
||||
self.io.tool_output(
|
||||
f"Approximate context window usage for {self.coder.main_model.name}, in tokens:"
|
||||
)
|
||||
self.io.tool_output()
|
||||
|
||||
width = 8
|
||||
|
@ -344,7 +364,7 @@ class Commands:
|
|||
total_cost = 0.0
|
||||
for tk, msg, tip in res:
|
||||
total += tk
|
||||
cost = tk * self.coder.main_model.info.get("input_cost_per_token", 0)
|
||||
cost = tk * (self.coder.main_model.info.get("input_cost_per_token") or 0)
|
||||
total_cost += cost
|
||||
msg = msg.ljust(col_width)
|
||||
self.io.tool_output(f"${cost:7.4f} {fmt(tk)} {msg} {tip}") # noqa: E231
|
||||
|
@ -352,7 +372,7 @@ class Commands:
|
|||
self.io.tool_output("=" * (width + cost_width + 1))
|
||||
self.io.tool_output(f"${total_cost:7.4f} {fmt(total)} tokens total") # noqa: E231
|
||||
|
||||
limit = self.coder.main_model.info.get("max_input_tokens", 0)
|
||||
limit = self.coder.main_model.info.get("max_input_tokens") or 0
|
||||
if not limit:
|
||||
return
|
||||
|
||||
|
@ -440,27 +460,36 @@ class Commands:
|
|||
# Get the current HEAD after undo
|
||||
current_head_hash = self.coder.repo.repo.head.commit.hexsha[:7]
|
||||
current_head_message = self.coder.repo.repo.head.commit.message.strip()
|
||||
self.io.tool_output(f"HEAD is: {current_head_hash} {current_head_message}")
|
||||
self.io.tool_output(f"Now at: {current_head_hash} {current_head_message}")
|
||||
|
||||
if self.coder.main_model.send_undo_reply:
|
||||
return prompts.undo_command_reply
|
||||
|
||||
def cmd_diff(self, args=""):
|
||||
"Display the diff of the last aider commit"
|
||||
"Display the diff of changes since the last message"
|
||||
if not self.coder.repo:
|
||||
self.io.tool_error("No git repository found.")
|
||||
return
|
||||
|
||||
last_commit_hash = self.coder.repo.repo.head.commit.hexsha[:7]
|
||||
|
||||
if last_commit_hash not in self.coder.aider_commit_hashes:
|
||||
self.io.tool_error(f"Last commit {last_commit_hash} was not an aider commit.")
|
||||
self.io.tool_error("You could try `/git diff` or `/git diff HEAD^`.")
|
||||
current_head = self.coder.repo.get_head()
|
||||
if current_head is None:
|
||||
self.io.tool_error("Unable to get current commit. The repository might be empty.")
|
||||
return
|
||||
|
||||
if len(self.coder.commit_before_message) < 2:
|
||||
commit_before_message = current_head + "^"
|
||||
else:
|
||||
commit_before_message = self.coder.commit_before_message[-2]
|
||||
|
||||
if not commit_before_message or commit_before_message == current_head:
|
||||
self.io.tool_error("No changes to display since the last message.")
|
||||
return
|
||||
|
||||
self.io.tool_output(f"Diff since {commit_before_message[:7]}...")
|
||||
|
||||
diff = self.coder.repo.diff_commits(
|
||||
self.coder.pretty,
|
||||
"HEAD^",
|
||||
commit_before_message,
|
||||
"HEAD",
|
||||
)
|
||||
|
||||
|
@ -472,6 +501,9 @@ class Commands:
|
|||
fname = f'"{fname}"'
|
||||
return fname
|
||||
|
||||
def completions_read(self):
|
||||
return self.completions_add()
|
||||
|
||||
def completions_add(self):
|
||||
files = set(self.coder.get_all_relative_files())
|
||||
files = files - set(self.coder.get_inchat_relative_files())
|
||||
|
@ -558,6 +590,18 @@ class Commands:
|
|||
|
||||
if abs_file_path in self.coder.abs_fnames:
|
||||
self.io.tool_error(f"{matched_file} is already in the chat")
|
||||
elif abs_file_path in self.coder.abs_read_only_fnames:
|
||||
if self.coder.repo and self.coder.repo.path_in_repo(matched_file):
|
||||
self.coder.abs_read_only_fnames.remove(abs_file_path)
|
||||
self.coder.abs_fnames.add(abs_file_path)
|
||||
self.io.tool_output(
|
||||
f"Moved {matched_file} from read-only to editable files in the chat"
|
||||
)
|
||||
added_fnames.append(matched_file)
|
||||
else:
|
||||
self.io.tool_error(
|
||||
f"Cannot add {matched_file} as it's not part of the repository"
|
||||
)
|
||||
else:
|
||||
if is_image_file(matched_file) and not self.coder.main_model.accepts_images:
|
||||
self.io.tool_error(
|
||||
|
@ -575,20 +619,12 @@ class Commands:
|
|||
self.coder.check_added_files()
|
||||
added_fnames.append(matched_file)
|
||||
|
||||
if not added_fnames:
|
||||
return
|
||||
|
||||
# only reply if there's been some chatting since the last edit
|
||||
if not self.coder.cur_messages:
|
||||
return
|
||||
|
||||
reply = prompts.added_files.format(fnames=", ".join(added_fnames))
|
||||
return reply
|
||||
|
||||
def completions_drop(self):
|
||||
files = self.coder.get_inchat_relative_files()
|
||||
files = [self.quote_fname(fn) for fn in files]
|
||||
return files
|
||||
read_only_files = [self.coder.get_rel_fname(fn) for fn in self.coder.abs_read_only_fnames]
|
||||
all_files = files + read_only_files
|
||||
all_files = [self.quote_fname(fn) for fn in all_files]
|
||||
return all_files
|
||||
|
||||
def cmd_drop(self, args=""):
|
||||
"Remove files from the chat session to free up context space"
|
||||
|
@ -596,9 +632,19 @@ class Commands:
|
|||
if not args.strip():
|
||||
self.io.tool_output("Dropping all files from the chat session.")
|
||||
self.coder.abs_fnames = set()
|
||||
self.coder.abs_read_only_fnames = set()
|
||||
return
|
||||
|
||||
filenames = parse_quoted_filenames(args)
|
||||
for word in filenames:
|
||||
# Handle read-only files separately, without glob_filtered_to_repo
|
||||
read_only_matched = [f for f in self.coder.abs_read_only_fnames if word in f]
|
||||
|
||||
if read_only_matched:
|
||||
for matched_file in read_only_matched:
|
||||
self.coder.abs_read_only_fnames.remove(matched_file)
|
||||
self.io.tool_output(f"Removed read-only file {matched_file} from the chat")
|
||||
|
||||
matched_files = self.glob_filtered_to_repo(word)
|
||||
|
||||
if not matched_files:
|
||||
|
@ -678,7 +724,7 @@ class Commands:
|
|||
add = result.returncode != 0
|
||||
else:
|
||||
response = self.io.prompt_ask(
|
||||
"Add the output to the chat? (y/n/instructions): ", default="y"
|
||||
"Add the output to the chat?\n(y/n/instructions)", default=""
|
||||
).strip()
|
||||
|
||||
if response.lower() in ["yes", "y"]:
|
||||
|
@ -718,6 +764,7 @@ class Commands:
|
|||
|
||||
other_files = []
|
||||
chat_files = []
|
||||
read_only_files = []
|
||||
for file in files:
|
||||
abs_file_path = self.coder.abs_root_path(file)
|
||||
if abs_file_path in self.coder.abs_fnames:
|
||||
|
@ -725,8 +772,13 @@ class Commands:
|
|||
else:
|
||||
other_files.append(file)
|
||||
|
||||
if not chat_files and not other_files:
|
||||
self.io.tool_output("\nNo files in chat or git repo.")
|
||||
# Add read-only files
|
||||
for abs_file_path in self.coder.abs_read_only_fnames:
|
||||
rel_file_path = self.coder.get_rel_fname(abs_file_path)
|
||||
read_only_files.append(rel_file_path)
|
||||
|
||||
if not chat_files and not other_files and not read_only_files:
|
||||
self.io.tool_output("\nNo files in chat, git repo, or read-only list.")
|
||||
return
|
||||
|
||||
if other_files:
|
||||
|
@ -734,6 +786,11 @@ class Commands:
|
|||
for file in other_files:
|
||||
self.io.tool_output(f" {file}")
|
||||
|
||||
if read_only_files:
|
||||
self.io.tool_output("\nRead-only files:\n")
|
||||
for file in read_only_files:
|
||||
self.io.tool_output(f" {file}")
|
||||
|
||||
if chat_files:
|
||||
self.io.tool_output("\nFiles in chat:\n")
|
||||
for file in chat_files:
|
||||
|
@ -787,13 +844,23 @@ class Commands:
|
|||
"""
|
||||
user_msg += "\n".join(self.coder.get_announcements()) + "\n"
|
||||
|
||||
assistant_msg = coder.run(user_msg)
|
||||
coder.run(user_msg, preproc=False)
|
||||
|
||||
self.coder.cur_messages += [
|
||||
dict(role="user", content=user_msg),
|
||||
dict(role="assistant", content=assistant_msg),
|
||||
]
|
||||
self.coder.total_cost += coder.total_cost
|
||||
if self.coder.repo_map:
|
||||
map_tokens = self.coder.repo_map.max_map_tokens
|
||||
map_mul_no_files = self.coder.repo_map.map_mul_no_files
|
||||
else:
|
||||
map_tokens = 0
|
||||
map_mul_no_files = 1
|
||||
|
||||
raise SwitchCoder(
|
||||
edit_format=self.coder.edit_format,
|
||||
summarize_from_coder=False,
|
||||
from_coder=coder,
|
||||
map_tokens=map_tokens,
|
||||
map_mul_no_files=map_mul_no_files,
|
||||
show_announcements=False,
|
||||
)
|
||||
|
||||
def clone(self):
|
||||
return Commands(
|
||||
|
@ -805,28 +872,35 @@ class Commands:
|
|||
|
||||
def cmd_ask(self, args):
|
||||
"Ask questions about the code base without editing any files"
|
||||
return self._generic_chat_command(args, "ask")
|
||||
|
||||
def cmd_code(self, args):
|
||||
"Ask for changes to your code"
|
||||
return self._generic_chat_command(args, self.coder.main_model.edit_format)
|
||||
|
||||
def _generic_chat_command(self, args, edit_format):
|
||||
if not args.strip():
|
||||
self.io.tool_error("Please provide a question or topic for the chat.")
|
||||
self.io.tool_error(f"Please provide a question or topic for the {edit_format} chat.")
|
||||
return
|
||||
|
||||
from aider.coders import Coder
|
||||
|
||||
chat_coder = Coder.create(
|
||||
coder = Coder.create(
|
||||
io=self.io,
|
||||
from_coder=self.coder,
|
||||
edit_format="ask",
|
||||
edit_format=edit_format,
|
||||
summarize_from_coder=False,
|
||||
)
|
||||
|
||||
user_msg = args
|
||||
assistant_msg = chat_coder.run(user_msg)
|
||||
coder.run(user_msg)
|
||||
|
||||
self.coder.cur_messages += [
|
||||
dict(role="user", content=user_msg),
|
||||
dict(role="assistant", content=assistant_msg),
|
||||
]
|
||||
self.coder.total_cost += chat_coder.total_cost
|
||||
raise SwitchCoder(
|
||||
edit_format=self.coder.edit_format,
|
||||
summarize_from_coder=False,
|
||||
from_coder=coder,
|
||||
show_announcements=False,
|
||||
)
|
||||
|
||||
def get_help_md(self):
|
||||
"Show help about all commands in markdown"
|
||||
|
@ -894,27 +968,82 @@ class Commands:
|
|||
|
||||
return text
|
||||
|
||||
def cmd_add_clipboard_image(self, args):
|
||||
"Add an image from the clipboard to the chat"
|
||||
def cmd_clipboard(self, args):
|
||||
"Add image/text from the clipboard to the chat (optionally provide a name for the image)"
|
||||
try:
|
||||
# Check for image first
|
||||
image = ImageGrab.grabclipboard()
|
||||
if image is None:
|
||||
self.io.tool_error("No image found in clipboard.")
|
||||
return
|
||||
if isinstance(image, Image.Image):
|
||||
if args.strip():
|
||||
filename = args.strip()
|
||||
ext = os.path.splitext(filename)[1].lower()
|
||||
if ext in (".jpg", ".jpeg", ".png"):
|
||||
basename = filename
|
||||
else:
|
||||
basename = f"{filename}.png"
|
||||
else:
|
||||
basename = "clipboard_image.png"
|
||||
|
||||
with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as temp_file:
|
||||
image.save(temp_file.name, "PNG")
|
||||
temp_file_path = temp_file.name
|
||||
temp_dir = tempfile.mkdtemp()
|
||||
temp_file_path = os.path.join(temp_dir, basename)
|
||||
image_format = "PNG" if basename.lower().endswith(".png") else "JPEG"
|
||||
image.save(temp_file_path, image_format)
|
||||
|
||||
abs_file_path = Path(temp_file_path).resolve()
|
||||
|
||||
# Check if a file with the same name already exists in the chat
|
||||
existing_file = next(
|
||||
(f for f in self.coder.abs_fnames if Path(f).name == abs_file_path.name), None
|
||||
)
|
||||
if existing_file:
|
||||
self.coder.abs_fnames.remove(existing_file)
|
||||
self.io.tool_output(f"Replaced existing image in the chat: {existing_file}")
|
||||
|
||||
self.coder.abs_fnames.add(str(abs_file_path))
|
||||
self.io.tool_output(f"Added clipboard image to the chat: {abs_file_path}")
|
||||
self.coder.check_added_files()
|
||||
|
||||
return prompts.added_files.format(fnames=str(abs_file_path))
|
||||
return
|
||||
|
||||
# If not an image, try to get text
|
||||
text = pyperclip.paste()
|
||||
if text:
|
||||
self.io.tool_output(text)
|
||||
return text
|
||||
|
||||
self.io.tool_error("No image or text content found in clipboard.")
|
||||
return
|
||||
|
||||
except Exception as e:
|
||||
self.io.tool_error(f"Error adding clipboard image: {e}")
|
||||
self.io.tool_error(f"Error processing clipboard content: {e}")
|
||||
|
||||
def cmd_read(self, args):
|
||||
"Add a file to the chat that is for reference, not to be edited"
|
||||
if not args.strip():
|
||||
self.io.tool_error("Please provide a filename to read.")
|
||||
return
|
||||
|
||||
filename = args.strip()
|
||||
abs_path = os.path.abspath(filename)
|
||||
|
||||
if not os.path.exists(abs_path):
|
||||
self.io.tool_error(f"File not found: {abs_path}")
|
||||
return
|
||||
|
||||
if not os.path.isfile(abs_path):
|
||||
self.io.tool_error(f"Not a file: {abs_path}")
|
||||
return
|
||||
|
||||
self.coder.abs_read_only_fnames.add(abs_path)
|
||||
self.io.tool_output(f"Added {abs_path} to read-only files.")
|
||||
|
||||
def cmd_map(self, args):
|
||||
"Print out the current repository map"
|
||||
repo_map = self.coder.get_repo_map()
|
||||
if repo_map:
|
||||
self.io.tool_output(repo_map)
|
||||
else:
|
||||
self.io.tool_output("No repository map available.")
|
||||
|
||||
|
||||
def expand_subdir(file_path):
|
||||
|
|
44
aider/io.py
44
aider/io.py
|
@ -15,6 +15,7 @@ from pygments.lexers import MarkdownLexer, guess_lexer_for_filename
|
|||
from pygments.token import Token
|
||||
from pygments.util import ClassNotFound
|
||||
from rich.console import Console
|
||||
from rich.style import Style as RichStyle
|
||||
from rich.text import Text
|
||||
|
||||
from .dump import dump # noqa: F401
|
||||
|
@ -22,10 +23,13 @@ from .utils import is_image_file
|
|||
|
||||
|
||||
class AutoCompleter(Completer):
|
||||
def __init__(self, root, rel_fnames, addable_rel_fnames, commands, encoding):
|
||||
def __init__(
|
||||
self, root, rel_fnames, addable_rel_fnames, commands, encoding, abs_read_only_fnames=None
|
||||
):
|
||||
self.addable_rel_fnames = addable_rel_fnames
|
||||
self.rel_fnames = rel_fnames
|
||||
self.encoding = encoding
|
||||
self.abs_read_only_fnames = abs_read_only_fnames or []
|
||||
|
||||
fname_to_rel_fnames = defaultdict(list)
|
||||
for rel_fname in addable_rel_fnames:
|
||||
|
@ -47,7 +51,11 @@ class AutoCompleter(Completer):
|
|||
for rel_fname in rel_fnames:
|
||||
self.words.add(rel_fname)
|
||||
|
||||
fname = Path(root) / rel_fname
|
||||
all_fnames = [Path(root) / rel_fname for rel_fname in rel_fnames]
|
||||
if abs_read_only_fnames:
|
||||
all_fnames.extend(abs_read_only_fnames)
|
||||
|
||||
for fname in all_fnames:
|
||||
try:
|
||||
with open(fname, "r", encoding=self.encoding) as f:
|
||||
content = f.read()
|
||||
|
@ -217,7 +225,7 @@ class InputOutput:
|
|||
with open(str(filename), "w", encoding=self.encoding) as f:
|
||||
f.write(content)
|
||||
|
||||
def get_input(self, root, rel_fnames, addable_rel_fnames, commands):
|
||||
def get_input(self, root, rel_fnames, addable_rel_fnames, commands, abs_read_only_fnames=None):
|
||||
if self.pretty:
|
||||
style = dict(style=self.user_input_color) if self.user_input_color else dict()
|
||||
self.console.rule(**style)
|
||||
|
@ -244,7 +252,12 @@ class InputOutput:
|
|||
style = None
|
||||
|
||||
completer_instance = AutoCompleter(
|
||||
root, rel_fnames, addable_rel_fnames, commands, self.encoding
|
||||
root,
|
||||
rel_fnames,
|
||||
addable_rel_fnames,
|
||||
commands,
|
||||
self.encoding,
|
||||
abs_read_only_fnames=abs_read_only_fnames,
|
||||
)
|
||||
|
||||
while True:
|
||||
|
@ -317,7 +330,7 @@ class InputOutput:
|
|||
def user_input(self, inp, log_only=True):
|
||||
if not log_only:
|
||||
style = dict(style=self.user_input_color) if self.user_input_color else dict()
|
||||
self.console.print(inp, **style)
|
||||
self.console.print(Text(inp), **style)
|
||||
|
||||
prefix = "####"
|
||||
if inp:
|
||||
|
@ -341,18 +354,19 @@ class InputOutput:
|
|||
self.num_user_asks += 1
|
||||
|
||||
if self.yes is True:
|
||||
res = "yes"
|
||||
res = "y"
|
||||
elif self.yes is False:
|
||||
res = "no"
|
||||
res = "n"
|
||||
else:
|
||||
res = prompt(question + " ", default=default)
|
||||
|
||||
hist = f"{question.strip()} {res.strip()}"
|
||||
res = res.lower().strip()
|
||||
is_yes = res in ("y", "yes")
|
||||
|
||||
hist = f"{question.strip()} {'y' if is_yes else 'n'}"
|
||||
self.append_chat_history(hist, linebreak=True, blockquote=True)
|
||||
|
||||
if not res or not res.strip():
|
||||
return
|
||||
return res.strip().lower().startswith("y")
|
||||
return is_yes
|
||||
|
||||
def prompt_ask(self, question, default=None):
|
||||
self.num_user_asks += 1
|
||||
|
@ -389,7 +403,7 @@ class InputOutput:
|
|||
style = dict(style=self.tool_error_color) if self.tool_error_color else dict()
|
||||
self.console.print(message, **style)
|
||||
|
||||
def tool_output(self, *messages, log_only=False):
|
||||
def tool_output(self, *messages, log_only=False, bold=False):
|
||||
if messages:
|
||||
hist = " ".join(messages)
|
||||
hist = f"{hist.strip()}"
|
||||
|
@ -397,8 +411,10 @@ class InputOutput:
|
|||
|
||||
if not log_only:
|
||||
messages = list(map(Text, messages))
|
||||
style = dict(style=self.tool_output_color) if self.tool_output_color else dict()
|
||||
self.console.print(*messages, **style)
|
||||
style = dict(color=self.tool_output_color) if self.tool_output_color else dict()
|
||||
style["reverse"] = bold
|
||||
style = RichStyle(**style)
|
||||
self.console.print(*messages, style=style)
|
||||
|
||||
def append_chat_history(self, text, linebreak=False, blockquote=False, strip=True):
|
||||
if blockquote:
|
||||
|
|
|
@ -4,8 +4,11 @@ import warnings
|
|||
|
||||
warnings.filterwarnings("ignore", category=UserWarning, module="pydantic")
|
||||
|
||||
os.environ["OR_SITE_URL"] = "http://aider.chat"
|
||||
os.environ["OR_APP_NAME"] = "Aider"
|
||||
AIDER_SITE_URL = "https://aider.chat"
|
||||
AIDER_APP_NAME = "Aider"
|
||||
|
||||
os.environ["OR_SITE_URL"] = AIDER_SITE_URL
|
||||
os.environ["OR_APP_NAME"] = AIDER_APP_NAME
|
||||
|
||||
# `import litellm` takes 1.5 seconds, defer it!
|
||||
|
||||
|
|
|
@ -384,6 +384,7 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
|
|||
|
||||
all_files = args.files + (args.file or [])
|
||||
fnames = [str(Path(fn).resolve()) for fn in all_files]
|
||||
read_only_fnames = [str(Path(fn).resolve()) for fn in (args.read or [])]
|
||||
if len(all_files) > 1:
|
||||
good = True
|
||||
for fname in all_files:
|
||||
|
@ -415,11 +416,11 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
|
|||
return main(argv, input, output, right_repo_root, return_coder=return_coder)
|
||||
|
||||
if args.just_check_update:
|
||||
update_available = check_version(io, just_check=True)
|
||||
update_available = check_version(io, just_check=True, verbose=args.verbose)
|
||||
return 0 if not update_available else 1
|
||||
|
||||
if args.check_update:
|
||||
check_version(io)
|
||||
check_version(io, verbose=args.verbose)
|
||||
|
||||
if args.models:
|
||||
models.print_matching_models(io, args.models)
|
||||
|
@ -475,12 +476,13 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
|
|||
repo = GitRepo(
|
||||
io,
|
||||
fnames,
|
||||
git_dname or ".",
|
||||
git_dname,
|
||||
args.aiderignore,
|
||||
models=main_model.commit_message_models(),
|
||||
attribute_author=args.attribute_author,
|
||||
attribute_committer=args.attribute_committer,
|
||||
attribute_commit_message=args.attribute_commit_message,
|
||||
attribute_commit_message_author=args.attribute_commit_message_author,
|
||||
attribute_commit_message_committer=args.attribute_commit_message_committer,
|
||||
commit_prompt=args.commit_prompt,
|
||||
subtree_only=args.subtree_only,
|
||||
)
|
||||
|
@ -501,7 +503,7 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
|
|||
io=io,
|
||||
repo=repo,
|
||||
fnames=fnames,
|
||||
pretty=args.pretty,
|
||||
read_only_fnames=read_only_fnames,
|
||||
show_diffs=args.show_diffs,
|
||||
auto_commits=args.auto_commits,
|
||||
dirty_commits=args.dirty_commits,
|
||||
|
@ -618,7 +620,14 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
|
|||
coder.run()
|
||||
return
|
||||
except SwitchCoder as switch:
|
||||
coder = Coder.create(io=io, from_coder=coder, **switch.kwargs)
|
||||
kwargs = dict(io=io, from_coder=coder)
|
||||
kwargs.update(switch.kwargs)
|
||||
if "show_announcements" in kwargs:
|
||||
del kwargs["show_announcements"]
|
||||
|
||||
coder = Coder.create(**kwargs)
|
||||
|
||||
if switch.kwargs.get("show_announcements") is not False:
|
||||
coder.show_announcements()
|
||||
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ import importlib
|
|||
import json
|
||||
import math
|
||||
import os
|
||||
import platform
|
||||
import sys
|
||||
from dataclasses import dataclass, fields
|
||||
from pathlib import Path
|
||||
|
@ -13,7 +14,7 @@ from PIL import Image
|
|||
|
||||
from aider import urls
|
||||
from aider.dump import dump # noqa: F401
|
||||
from aider.llm import litellm
|
||||
from aider.llm import AIDER_APP_NAME, AIDER_SITE_URL, litellm
|
||||
|
||||
DEFAULT_MODEL_NAME = "gpt-4o"
|
||||
|
||||
|
@ -70,7 +71,6 @@ class ModelSettings:
|
|||
lazy: bool = False
|
||||
reminder_as_sys_msg: bool = False
|
||||
examples_as_sys_msg: bool = False
|
||||
can_prefill: bool = False
|
||||
extra_headers: Optional[dict] = None
|
||||
max_tokens: Optional[int] = None
|
||||
|
||||
|
@ -152,6 +152,16 @@ MODEL_SETTINGS = [
|
|||
lazy=True,
|
||||
reminder_as_sys_msg=True,
|
||||
),
|
||||
ModelSettings(
|
||||
"gpt-4o-2024-08-06",
|
||||
"diff",
|
||||
weak_model_name="gpt-4o-mini",
|
||||
use_repo_map=True,
|
||||
send_undo_reply=True,
|
||||
accepts_images=True,
|
||||
lazy=True,
|
||||
reminder_as_sys_msg=True,
|
||||
),
|
||||
ModelSettings(
|
||||
"gpt-4o",
|
||||
"diff",
|
||||
|
@ -238,7 +248,6 @@ MODEL_SETTINGS = [
|
|||
weak_model_name="claude-3-haiku-20240307",
|
||||
use_repo_map=True,
|
||||
send_undo_reply=True,
|
||||
can_prefill=True,
|
||||
),
|
||||
ModelSettings(
|
||||
"openrouter/anthropic/claude-3-opus",
|
||||
|
@ -246,13 +255,11 @@ MODEL_SETTINGS = [
|
|||
weak_model_name="openrouter/anthropic/claude-3-haiku",
|
||||
use_repo_map=True,
|
||||
send_undo_reply=True,
|
||||
can_prefill=True,
|
||||
),
|
||||
ModelSettings(
|
||||
"claude-3-sonnet-20240229",
|
||||
"whole",
|
||||
weak_model_name="claude-3-haiku-20240307",
|
||||
can_prefill=True,
|
||||
),
|
||||
ModelSettings(
|
||||
"claude-3-5-sonnet-20240620",
|
||||
|
@ -260,7 +267,6 @@ MODEL_SETTINGS = [
|
|||
weak_model_name="claude-3-haiku-20240307",
|
||||
use_repo_map=True,
|
||||
examples_as_sys_msg=True,
|
||||
can_prefill=True,
|
||||
accepts_images=True,
|
||||
max_tokens=8192,
|
||||
extra_headers={"anthropic-beta": "max-tokens-3-5-sonnet-2024-07-15"},
|
||||
|
@ -271,9 +277,12 @@ MODEL_SETTINGS = [
|
|||
weak_model_name="claude-3-haiku-20240307",
|
||||
use_repo_map=True,
|
||||
examples_as_sys_msg=True,
|
||||
can_prefill=True,
|
||||
max_tokens=8192,
|
||||
extra_headers={"anthropic-beta": "max-tokens-3-5-sonnet-2024-07-15"},
|
||||
extra_headers={
|
||||
"anthropic-beta": "max-tokens-3-5-sonnet-2024-07-15",
|
||||
"HTTP-Referer": AIDER_SITE_URL,
|
||||
"X-Title": AIDER_APP_NAME,
|
||||
},
|
||||
),
|
||||
ModelSettings(
|
||||
"openrouter/anthropic/claude-3.5-sonnet",
|
||||
|
@ -281,10 +290,13 @@ MODEL_SETTINGS = [
|
|||
weak_model_name="openrouter/anthropic/claude-3-haiku-20240307",
|
||||
use_repo_map=True,
|
||||
examples_as_sys_msg=True,
|
||||
can_prefill=True,
|
||||
accepts_images=True,
|
||||
max_tokens=8192,
|
||||
extra_headers={"anthropic-beta": "max-tokens-3-5-sonnet-2024-07-15"},
|
||||
extra_headers={
|
||||
"anthropic-beta": "max-tokens-3-5-sonnet-2024-07-15",
|
||||
"HTTP-Referer": "https://aider.chat",
|
||||
"X-Title": "Aider",
|
||||
},
|
||||
),
|
||||
# Vertex AI Claude models
|
||||
# Does not yet support 8k token
|
||||
|
@ -294,7 +306,6 @@ MODEL_SETTINGS = [
|
|||
weak_model_name="vertex_ai/claude-3-haiku@20240307",
|
||||
use_repo_map=True,
|
||||
examples_as_sys_msg=True,
|
||||
can_prefill=True,
|
||||
accepts_images=True,
|
||||
),
|
||||
ModelSettings(
|
||||
|
@ -303,13 +314,11 @@ MODEL_SETTINGS = [
|
|||
weak_model_name="vertex_ai/claude-3-haiku@20240307",
|
||||
use_repo_map=True,
|
||||
send_undo_reply=True,
|
||||
can_prefill=True,
|
||||
),
|
||||
ModelSettings(
|
||||
"vertex_ai/claude-3-sonnet@20240229",
|
||||
"whole",
|
||||
weak_model_name="vertex_ai/claude-3-haiku@20240307",
|
||||
can_prefill=True,
|
||||
),
|
||||
# Cohere
|
||||
ModelSettings(
|
||||
|
@ -405,9 +414,7 @@ class Model:
|
|||
self.missing_keys = res.get("missing_keys")
|
||||
self.keys_in_environment = res.get("keys_in_environment")
|
||||
|
||||
max_input_tokens = self.info.get("max_input_tokens")
|
||||
if not max_input_tokens:
|
||||
max_input_tokens = 0
|
||||
max_input_tokens = self.info.get("max_input_tokens") or 0
|
||||
if max_input_tokens < 32 * 1024:
|
||||
self.max_chat_history_tokens = 1024
|
||||
else:
|
||||
|
@ -470,14 +477,10 @@ class Model:
|
|||
if "gpt-3.5" in model or "gpt-4" in model:
|
||||
self.reminder_as_sys_msg = True
|
||||
|
||||
if "anthropic" in model:
|
||||
self.can_prefill = True
|
||||
|
||||
if "3.5-sonnet" in model or "3-5-sonnet" in model:
|
||||
self.edit_format = "diff"
|
||||
self.use_repo_map = True
|
||||
self.examples_as_sys_msg = True
|
||||
self.can_prefill = True
|
||||
|
||||
# use the defaults
|
||||
if self.edit_format == "diff":
|
||||
|
@ -512,6 +515,9 @@ class Model:
|
|||
return litellm.encode(model=self.name, text=text)
|
||||
|
||||
def token_count(self, messages):
|
||||
if type(messages) is list:
|
||||
return litellm.token_counter(model=self.name, messages=messages)
|
||||
|
||||
if not self.tokenizer:
|
||||
return
|
||||
|
||||
|
@ -669,6 +675,13 @@ def sanity_check_model(io, model):
|
|||
io.tool_error(f"Model {model}: Missing these environment variables:")
|
||||
for key in model.missing_keys:
|
||||
io.tool_error(f"- {key}")
|
||||
|
||||
if platform.system() == "Windows" or True:
|
||||
io.tool_output(
|
||||
"If you just set these environment variables using `setx` you may need to restart"
|
||||
" your terminal or command prompt for the changes to take effect."
|
||||
)
|
||||
|
||||
elif not model.keys_in_environment:
|
||||
show = True
|
||||
io.tool_output(f"Model {model}: Unknown which environment variables are required.")
|
||||
|
|
|
@ -29,7 +29,8 @@ class GitRepo:
|
|||
models=None,
|
||||
attribute_author=True,
|
||||
attribute_committer=True,
|
||||
attribute_commit_message=False,
|
||||
attribute_commit_message_author=False,
|
||||
attribute_commit_message_committer=False,
|
||||
commit_prompt=None,
|
||||
subtree_only=False,
|
||||
):
|
||||
|
@ -41,7 +42,8 @@ class GitRepo:
|
|||
|
||||
self.attribute_author = attribute_author
|
||||
self.attribute_committer = attribute_committer
|
||||
self.attribute_commit_message = attribute_commit_message
|
||||
self.attribute_commit_message_author = attribute_commit_message_author
|
||||
self.attribute_commit_message_committer = attribute_commit_message_committer
|
||||
self.commit_prompt = commit_prompt
|
||||
self.subtree_only = subtree_only
|
||||
self.ignore_file_cache = {}
|
||||
|
@ -98,7 +100,9 @@ class GitRepo:
|
|||
else:
|
||||
commit_message = self.get_commit_message(diffs, context)
|
||||
|
||||
if aider_edits and self.attribute_commit_message:
|
||||
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
|
||||
|
||||
if not commit_message:
|
||||
|
@ -130,7 +134,7 @@ class GitRepo:
|
|||
|
||||
self.repo.git.commit(cmd)
|
||||
commit_hash = self.repo.head.commit.hexsha[:7]
|
||||
self.io.tool_output(f"Commit {commit_hash} {commit_message}")
|
||||
self.io.tool_output(f"Commit {commit_hash} {commit_message}", bold=True)
|
||||
|
||||
# Restore the env
|
||||
|
||||
|
@ -155,10 +159,6 @@ class GitRepo:
|
|||
return self.repo.git_dir
|
||||
|
||||
def get_commit_message(self, diffs, context):
|
||||
if len(diffs) >= 4 * 1024 * 4:
|
||||
self.io.tool_error("Diff is too large to generate a commit message.")
|
||||
return
|
||||
|
||||
diffs = "# Diffs:\n" + diffs
|
||||
|
||||
content = ""
|
||||
|
@ -172,7 +172,12 @@ class GitRepo:
|
|||
dict(role="user", content=content),
|
||||
]
|
||||
|
||||
commit_message = None
|
||||
for model in self.models:
|
||||
num_tokens = model.token_count(messages)
|
||||
max_tokens = model.info.get("max_input_tokens") or 0
|
||||
if max_tokens and num_tokens > max_tokens:
|
||||
continue
|
||||
commit_message = simple_send_with_retries(model.name, messages)
|
||||
if commit_message:
|
||||
break
|
||||
|
@ -226,6 +231,8 @@ class GitRepo:
|
|||
args = []
|
||||
if pretty:
|
||||
args += ["--color"]
|
||||
else:
|
||||
args += ["--color=never"]
|
||||
|
||||
args += [from_commit, to_commit]
|
||||
diffs = self.repo.git.diff(*args)
|
||||
|
@ -355,3 +362,9 @@ class GitRepo:
|
|||
return True
|
||||
|
||||
return self.repo.is_dirty(path=path)
|
||||
|
||||
def get_head(self):
|
||||
try:
|
||||
return self.repo.head.commit.hexsha
|
||||
except ValueError:
|
||||
return None
|
||||
|
|
|
@ -60,6 +60,9 @@ class RepoMap:
|
|||
|
||||
self.main_model = main_model
|
||||
|
||||
self.tree_cache = {}
|
||||
self.tree_context_cache = {}
|
||||
|
||||
def token_count(self, text):
|
||||
len_text = len(text)
|
||||
if len_text < 200:
|
||||
|
@ -471,6 +474,7 @@ class RepoMap:
|
|||
if key in self.tree_cache:
|
||||
return self.tree_cache[key]
|
||||
|
||||
if rel_fname not in self.tree_context_cache:
|
||||
code = self.io.read_text(abs_fname) or ""
|
||||
if not code.endswith("\n"):
|
||||
code += "\n"
|
||||
|
@ -488,7 +492,10 @@ class RepoMap:
|
|||
# header_max=30,
|
||||
show_top_of_file_parent_scope=False,
|
||||
)
|
||||
self.tree_context_cache[rel_fname] = context
|
||||
|
||||
context = self.tree_context_cache[rel_fname]
|
||||
context.lines_of_interest = set()
|
||||
context.add_lines_of_interest(lois)
|
||||
context.add_context()
|
||||
res = context.format()
|
||||
|
|
|
@ -87,26 +87,48 @@ class Scraper:
|
|||
|
||||
def scrape(self, url):
|
||||
"""
|
||||
Scrape a url and turn it into readable markdown.
|
||||
Scrape a url and turn it into readable markdown if it's HTML.
|
||||
If it's plain text or non-HTML, return it as-is.
|
||||
|
||||
`url` - the URL to scrape.
|
||||
"""
|
||||
|
||||
if self.playwright_available:
|
||||
content = self.scrape_with_playwright(url)
|
||||
content, mime_type = self.scrape_with_playwright(url)
|
||||
else:
|
||||
content = self.scrape_with_httpx(url)
|
||||
content, mime_type = self.scrape_with_httpx(url)
|
||||
|
||||
if not content:
|
||||
self.print_error(f"Failed to retrieve content from {url}")
|
||||
return None
|
||||
|
||||
# Check if the content is HTML based on MIME type or content
|
||||
if (mime_type and mime_type.startswith("text/html")) or (
|
||||
mime_type is None and self.looks_like_html(content)
|
||||
):
|
||||
self.try_pandoc()
|
||||
|
||||
content = self.html_to_markdown(content)
|
||||
|
||||
return content
|
||||
|
||||
def looks_like_html(self, content):
|
||||
"""
|
||||
Check if the content looks like HTML.
|
||||
"""
|
||||
if isinstance(content, str):
|
||||
# Check for common HTML tags
|
||||
html_patterns = [
|
||||
r"<!DOCTYPE\s+html",
|
||||
r"<html",
|
||||
r"<head",
|
||||
r"<body",
|
||||
r"<div",
|
||||
r"<p>",
|
||||
r"<a\s+href=",
|
||||
]
|
||||
return any(re.search(pattern, content, re.IGNORECASE) for pattern in html_patterns)
|
||||
return False
|
||||
|
||||
# Internals...
|
||||
def scrape_with_playwright(self, url):
|
||||
import playwright
|
||||
|
@ -118,7 +140,7 @@ class Scraper:
|
|||
except Exception as e:
|
||||
self.playwright_available = False
|
||||
self.print_error(str(e))
|
||||
return
|
||||
return None, None
|
||||
|
||||
try:
|
||||
context = browser.new_context(ignore_https_errors=not self.verify_ssl)
|
||||
|
@ -131,23 +153,28 @@ class Scraper:
|
|||
|
||||
page.set_extra_http_headers({"User-Agent": user_agent})
|
||||
|
||||
response = None
|
||||
try:
|
||||
page.goto(url, wait_until="networkidle", timeout=5000)
|
||||
response = page.goto(url, wait_until="networkidle", timeout=5000)
|
||||
except playwright._impl._errors.TimeoutError:
|
||||
self.print_error(f"Timeout while loading {url}")
|
||||
except playwright._impl._errors.Error as e:
|
||||
self.print_error(f"Error navigating to {url}: {str(e)}")
|
||||
return None
|
||||
return None, None
|
||||
|
||||
try:
|
||||
content = page.content()
|
||||
mime_type = (
|
||||
response.header_value("content-type").split(";")[0] if response else None
|
||||
)
|
||||
except playwright._impl._errors.Error as e:
|
||||
self.print_error(f"Error retrieving page content: {str(e)}")
|
||||
content = None
|
||||
mime_type = None
|
||||
finally:
|
||||
browser.close()
|
||||
|
||||
return content
|
||||
return content, mime_type
|
||||
|
||||
def scrape_with_httpx(self, url):
|
||||
import httpx
|
||||
|
@ -157,12 +184,12 @@ class Scraper:
|
|||
with httpx.Client(headers=headers, verify=self.verify_ssl) as client:
|
||||
response = client.get(url)
|
||||
response.raise_for_status()
|
||||
return response.text
|
||||
return response.text, response.headers.get("content-type", "").split(";")[0]
|
||||
except httpx.HTTPError as http_err:
|
||||
self.print_error(f"HTTP error occurred: {http_err}")
|
||||
except Exception as err:
|
||||
self.print_error(f"An error occurred: {err}")
|
||||
return None
|
||||
return None, None
|
||||
|
||||
def try_pandoc(self):
|
||||
if self.pandoc_available:
|
||||
|
|
|
@ -14,13 +14,10 @@ CACHE = None
|
|||
# CACHE = Cache(CACHE_PATH)
|
||||
|
||||
|
||||
def lazy_litellm_retry_decorator(func):
|
||||
def wrapper(*args, **kwargs):
|
||||
def retry_exceptions():
|
||||
import httpx
|
||||
|
||||
decorated_func = backoff.on_exception(
|
||||
backoff.expo,
|
||||
(
|
||||
return (
|
||||
httpx.ConnectError,
|
||||
httpx.RemoteProtocolError,
|
||||
httpx.ReadTimeout,
|
||||
|
@ -31,7 +28,14 @@ def lazy_litellm_retry_decorator(func):
|
|||
litellm.exceptions.Timeout,
|
||||
litellm.exceptions.InternalServerError,
|
||||
litellm.llms.anthropic.AnthropicError,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def lazy_litellm_retry_decorator(func):
|
||||
def wrapper(*args, **kwargs):
|
||||
decorated_func = backoff.on_exception(
|
||||
backoff.expo,
|
||||
retry_exceptions(),
|
||||
max_time=60,
|
||||
on_backoff=lambda details: print(
|
||||
f"{details.get('exception', 'Exception')}\nRetry in {details['wait']:.1f} seconds."
|
||||
|
@ -42,8 +46,7 @@ def lazy_litellm_retry_decorator(func):
|
|||
return wrapper
|
||||
|
||||
|
||||
@lazy_litellm_retry_decorator
|
||||
def send_with_retries(
|
||||
def send_completion(
|
||||
model_name, messages, functions, stream, temperature=0, extra_headers=None, max_tokens=None
|
||||
):
|
||||
from aider.llm import litellm
|
||||
|
@ -54,6 +57,7 @@ def send_with_retries(
|
|||
temperature=temperature,
|
||||
stream=stream,
|
||||
)
|
||||
|
||||
if functions is not None:
|
||||
kwargs["tools"] = [dict(type="functions", function=functions[0])]
|
||||
if extra_headers is not None:
|
||||
|
@ -79,9 +83,10 @@ def send_with_retries(
|
|||
return hash_object, res
|
||||
|
||||
|
||||
@lazy_litellm_retry_decorator
|
||||
def simple_send_with_retries(model_name, messages):
|
||||
try:
|
||||
_hash, response = send_with_retries(
|
||||
_hash, response = send_completion(
|
||||
model_name=model_name,
|
||||
messages=messages,
|
||||
functions=None,
|
||||
|
|
|
@ -44,7 +44,7 @@ class ChdirTemporaryDirectory(IgnorantTemporaryDirectory):
|
|||
|
||||
def __enter__(self):
|
||||
res = super().__enter__()
|
||||
os.chdir(self.temp_dir.name)
|
||||
os.chdir(Path(self.temp_dir.name).resolve())
|
||||
return res
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
|
@ -112,13 +112,19 @@ def format_messages(messages, title=None):
|
|||
content = msg.get("content")
|
||||
if isinstance(content, list): # Handle list content (e.g., image messages)
|
||||
for item in content:
|
||||
if isinstance(item, dict) and "image_url" in item:
|
||||
output.append(f"{role} Image URL: {item['image_url']['url']}")
|
||||
if isinstance(item, dict):
|
||||
for key, value in item.items():
|
||||
if isinstance(value, dict) and "url" in value:
|
||||
output.append(f"{role} {key.capitalize()} URL: {value['url']}")
|
||||
else:
|
||||
output.append(f"{role} {key}: {value}")
|
||||
else:
|
||||
output.append(f"{role} {item}")
|
||||
elif isinstance(content, str): # Handle string content
|
||||
output.append(format_content(role, content))
|
||||
content = msg.get("function_call")
|
||||
if content:
|
||||
output.append(f"{role} {content}")
|
||||
function_call = msg.get("function_call")
|
||||
if function_call:
|
||||
output.append(f"{role} Function Call: {function_call}")
|
||||
|
||||
return "\n".join(output)
|
||||
|
||||
|
|
|
@ -10,12 +10,15 @@ from aider import utils
|
|||
from aider.dump import dump # noqa: F401
|
||||
|
||||
|
||||
def check_version(io, just_check=False):
|
||||
def check_version(io, just_check=False, verbose=False):
|
||||
fname = Path.home() / ".aider" / "caches" / "versioncheck"
|
||||
if not just_check and fname.exists():
|
||||
day = 60 * 60 * 24
|
||||
since = time.time() - fname.stat().st_mtime
|
||||
if since < day:
|
||||
if verbose:
|
||||
hours = since / 60 / 60
|
||||
io.tool_output(f"Too soon to check version: {hours:.1f} hours")
|
||||
return
|
||||
|
||||
# To keep startup fast, avoid importing this unless needed
|
||||
|
@ -27,7 +30,7 @@ def check_version(io, just_check=False):
|
|||
latest_version = data["info"]["version"]
|
||||
current_version = aider.__version__
|
||||
|
||||
if just_check:
|
||||
if just_check or verbose:
|
||||
io.tool_output(f"Current version: {current_version}")
|
||||
io.tool_output(f"Latest version: {latest_version}")
|
||||
|
||||
|
@ -41,11 +44,13 @@ def check_version(io, just_check=False):
|
|||
fname.parent.mkdir(parents=True, exist_ok=True)
|
||||
fname.touch()
|
||||
|
||||
if just_check:
|
||||
if just_check or verbose:
|
||||
if is_update_available:
|
||||
io.tool_output("Update available")
|
||||
else:
|
||||
io.tool_output("No update available")
|
||||
|
||||
if just_check:
|
||||
return is_update_available
|
||||
|
||||
if not is_update_available:
|
||||
|
|
|
@ -16,6 +16,51 @@ cog.out(text)
|
|||
|
||||
# Release history
|
||||
|
||||
### Aider v0.50.0
|
||||
|
||||
- Infinite output for DeepSeek Coder, Mistral models in addition to Anthropic's models.
|
||||
- New `--deepseek` switch to use DeepSeek Coder.
|
||||
- DeepSeek Coder uses 8k token output.
|
||||
- New `--chat-mode <mode>` switch to launch in ask/help/code modes.
|
||||
- New `/code <message>` command request a code edit while in `ask` mode.
|
||||
- Web scraper is more robust if page never idles.
|
||||
- Improved token and cost reporting for infinite output.
|
||||
- Improvements and bug fixes for `/read` only files.
|
||||
- Switched from `setup.py` to `pyproject.toml`, by @branchvincent.
|
||||
- Bug fix to persist files added during `/ask`.
|
||||
- Bug fix for chat history size in `/tokens`.
|
||||
- Aider wrote 66% of the code in this release.
|
||||
|
||||
### Aider v0.49.1
|
||||
|
||||
- Bugfix to `/help`.
|
||||
|
||||
### Aider v0.49.0
|
||||
|
||||
- Add read-only files to the chat context with `/read` and `--read`, including from outside the git repo.
|
||||
- `/diff` now shows diffs of all changes resulting from your request, including lint and test fixes.
|
||||
- New `/clipboard` command to paste images or text from the clipboard, replaces `/add-clipboard-image`.
|
||||
- Now shows the markdown scraped when you add a url with `/web`.
|
||||
- When [scripting aider](https://aider.chat/docs/scripting.html) messages can now contain in-chat `/` commands.
|
||||
- Aider in docker image now suggests the correct command to update to latest version.
|
||||
- Improved retries on API errors (was easy to test during Sonnet outage).
|
||||
- Added `--mini` for `gpt-4o-mini`.
|
||||
- Bugfix to keep session cost accurate when using `/ask` and `/help`.
|
||||
- Performance improvements for repo map calculation.
|
||||
- `/tokens` now shows the active model.
|
||||
- Enhanced commit message attribution options:
|
||||
- New `--attribute-commit-message-author` to prefix commit messages with 'aider: ' if aider authored the changes, replaces `--attribute-commit-message`.
|
||||
- New `--attribute-commit-message-committer` to prefix all commit messages with 'aider: '.
|
||||
- Aider wrote 61% of the code in this release.
|
||||
|
||||
### Aider v0.48.1
|
||||
|
||||
- Added `openai/gpt-4o-2024-08-06`.
|
||||
- Worked around litellm bug that removes OpenRouter app headers when using `extra_headers`.
|
||||
- Improved progress indication during repo map processing.
|
||||
- Corrected instructions for upgrading the docker container to latest aider version.
|
||||
- Removed obsolete 16k token limit on commit diffs, use per-model limits.
|
||||
|
||||
### Aider v0.48.0
|
||||
|
||||
- Performance improvements for large/mono repos.
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,90 +1,126 @@
|
|||
<canvas id="blameChart" width="800" height="450" style="margin-top: 20px"></canvas>
|
||||
<canvas id="blameChart" width="800" height="360" style="margin-top: 20px"></canvas>
|
||||
<canvas id="linesChart" width="800" height="360" style="margin-top: 20px"></canvas>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/moment"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-moment"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
var ctx = document.getElementById('blameChart').getContext('2d');
|
||||
var blameCtx = document.getElementById('blameChart').getContext('2d');
|
||||
var linesCtx = document.getElementById('linesChart').getContext('2d');
|
||||
|
||||
var labels = [{% for row in site.data.blame %}'{{ row.end_tag }}',{% endfor %}];
|
||||
|
||||
var blameData = {
|
||||
labels: labels,
|
||||
datasets: [{
|
||||
label: 'Aider\'s Contribution to Each Release',
|
||||
data: [
|
||||
{% for row in site.data.blame %}
|
||||
{
|
||||
x: '{{ row.end_date }}',
|
||||
y: {{ row.aider_percentage }},
|
||||
r: Math.sqrt({{ row.aider_total }}) * 1.5,
|
||||
label: '{{ row.end_tag }}',
|
||||
percentage: {{ row.aider_percentage }},
|
||||
lines: {{ row.aider_total }}
|
||||
},
|
||||
{% endfor %}
|
||||
],
|
||||
backgroundColor: 'rgba(54, 162, 235, 0.2)',
|
||||
label: 'Aider\'s percent of new code by release',
|
||||
data: [{% for row in site.data.blame %}{ x: '{{ row.end_tag }}', y: {{ row.aider_percentage }}, lines: {{ row.aider_total }} },{% endfor %}],
|
||||
backgroundColor: 'rgba(54, 162, 235, 0.8)',
|
||||
borderColor: 'rgba(54, 162, 235, 1)',
|
||||
borderWidth: 1
|
||||
}]
|
||||
};
|
||||
|
||||
var blameChart = new Chart(ctx, {
|
||||
type: 'bubble',
|
||||
var linesData = {
|
||||
labels: labels,
|
||||
datasets: [{
|
||||
label: 'Aider\'s lines of new code',
|
||||
data: [{% for row in site.data.blame %}{ x: '{{ row.end_tag }}', y: {{ row.aider_total }} },{% endfor %}],
|
||||
backgroundColor: 'rgba(255, 99, 132, 0.8)',
|
||||
borderColor: 'rgba(255, 99, 132, 1)',
|
||||
borderWidth: 1
|
||||
}]
|
||||
};
|
||||
|
||||
var blameChart = new Chart(blameCtx, {
|
||||
type: 'bar',
|
||||
data: blameData,
|
||||
options: {
|
||||
scales: {
|
||||
x: {
|
||||
type: 'time',
|
||||
time: {
|
||||
unit: 'month',
|
||||
displayFormats: {
|
||||
month: 'MMM YYYY'
|
||||
}
|
||||
},
|
||||
type: 'category',
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Release date'
|
||||
text: 'Version'
|
||||
},
|
||||
ticks: {
|
||||
maxRotation: 45,
|
||||
minRotation: 45
|
||||
},
|
||||
min: moment('{{ site.data.blame | first | map: "end_date" | first }}').subtract(1, 'month'),
|
||||
max: moment('{{ site.data.blame | last | map: "end_date" | first }}').add(1, 'month')
|
||||
}
|
||||
},
|
||||
y: {
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Aider Contribution (% of code)'
|
||||
text: 'Percent of new code'
|
||||
},
|
||||
beginAtZero: true
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false
|
||||
},
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
label: function(context) {
|
||||
return `${context.raw.label}: ${Math.round(context.raw.percentage)}% (${context.raw.lines} lines)`;
|
||||
}
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
display: true,
|
||||
position: 'top',
|
||||
labels: {
|
||||
generateLabels: function(chart) {
|
||||
return [{
|
||||
text: 'Bubble size: Lines of code contributed by aider',
|
||||
fillStyle: 'rgba(54, 162, 235, 0.2)',
|
||||
strokeStyle: 'rgba(54, 162, 235, 1)',
|
||||
lineWidth: 1,
|
||||
hidden: false,
|
||||
index: 0
|
||||
}];
|
||||
var label = 'Aider\'s contribution';
|
||||
var value = context.parsed.y || 0;
|
||||
var lines = context.raw.lines || 0;
|
||||
return `${label}: ${Math.round(value)}% (${lines} lines)`;
|
||||
}
|
||||
}
|
||||
},
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Aider\'s Contribution to Each Release',
|
||||
text: 'Percent of new code written by aider, by release',
|
||||
font: {
|
||||
size: 16
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var linesChart = new Chart(linesCtx, {
|
||||
type: 'bar',
|
||||
data: linesData,
|
||||
options: {
|
||||
scales: {
|
||||
x: {
|
||||
type: 'category',
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Version'
|
||||
},
|
||||
ticks: {
|
||||
maxRotation: 45,
|
||||
minRotation: 45
|
||||
}
|
||||
},
|
||||
y: {
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Lines of new code'
|
||||
},
|
||||
beginAtZero: true
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false
|
||||
},
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
label: function(context) {
|
||||
var label = 'New lines of code by aider';
|
||||
var value = context.parsed.y || 0;
|
||||
return `${label}: ${value}`;
|
||||
}
|
||||
}
|
||||
},
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Lines of new code written by aider, by release',
|
||||
font: {
|
||||
size: 16
|
||||
}
|
||||
|
|
|
@ -2,16 +2,16 @@
|
|||
You can get started quickly like this:
|
||||
|
||||
```
|
||||
$ pip install aider-chat
|
||||
python -m pip install aider-chat
|
||||
|
||||
# Change directory into a git repo
|
||||
$ cd /to/your/git/repo
|
||||
cd /to/your/git/repo
|
||||
|
||||
# Work with Claude 3.5 Sonnet on your repo
|
||||
$ export ANTHROPIC_API_KEY=your-key-goes-here
|
||||
$ aider
|
||||
export ANTHROPIC_API_KEY=your-key-goes-here
|
||||
aider
|
||||
|
||||
# Work with GPT-4o on your repo
|
||||
$ export OPENAI_API_KEY=your-key-goes-here
|
||||
$ aider
|
||||
export OPENAI_API_KEY=your-key-goes-here
|
||||
aider
|
||||
```
|
||||
|
|
|
@ -44,6 +44,10 @@ Model azure/gpt-4-turbo: Missing these environment variables:
|
|||
- AZURE_API_KEY
|
||||
```
|
||||
|
||||
{: .tip }
|
||||
On Windows,
|
||||
if you just set these environment variables using `setx` you may need to restart your terminal or
|
||||
command prompt for the changes to take effect.
|
||||
|
||||
|
||||
## Unknown which environment variables are required
|
||||
|
|
5
aider/website/_includes/python-m-aider.md
Normal file
5
aider/website/_includes/python-m-aider.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
{: .tip }
|
||||
In some environments you may get "aider command not found" errors.
|
||||
You can try `python -m aider` or
|
||||
[see here for more info](/docs/troubleshooting/aider-not-found.html).
|
||||
|
7
aider/website/_includes/venv-pipx.md
Normal file
7
aider/website/_includes/venv-pipx.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
{: .tip }
|
||||
Using a Python
|
||||
[virtual environment](https://docs.python.org/3/library/venv.html){:target="_blank"}
|
||||
is recommended.
|
||||
Or, you could
|
||||
[use pipx to install aider](/docs/install/pipx.html)
|
||||
once for your whole system.
|
|
@ -209,7 +209,7 @@ that aider originally used.
|
|||
Switching from ctags to tree-sitter provides a bunch of benefits:
|
||||
|
||||
- The map is richer, showing full function call signatures and other details straight from the source files.
|
||||
- Thanks to `py-tree-sitter-languages`, we get full support for many programming languages via a python package that's automatically installed as part of the normal `pip install aider-chat`.
|
||||
- Thanks to `py-tree-sitter-languages`, we get full support for many programming languages via a python package that's automatically installed as part of the normal `python -m pip install aider-chat`.
|
||||
- We remove the requirement for users to manually install `universal-ctags` via some external tool or package manager (brew, apt, choco, etc).
|
||||
- Tree-sitter integration is a key enabler for future work and capabilities for aider.
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ making it the best available model for pair programming with AI.
|
|||
To use Claude 3 Opus with aider:
|
||||
|
||||
```
|
||||
pip install aider-chat
|
||||
python -m pip install aider-chat
|
||||
export ANTHROPIC_API_KEY=sk-...
|
||||
aider --opus
|
||||
```
|
||||
|
|
|
@ -46,7 +46,7 @@ It also supports [connecting to almost any LLM](https://aider.chat/docs/llms.htm
|
|||
Use the `--browser` switch to launch the browser version of aider:
|
||||
|
||||
```
|
||||
pip install aider-chat
|
||||
python -m pip install aider-chat
|
||||
|
||||
export OPENAI_API_KEY=<key> # Mac/Linux
|
||||
setx OPENAI_API_KEY <key> # Windows, restart shell after setx
|
||||
|
|
|
@ -116,7 +116,7 @@ for more details, but
|
|||
you can get started quickly with aider and Sonnet like this:
|
||||
|
||||
```
|
||||
$ pip install aider-chat
|
||||
$ python -m pip install aider-chat
|
||||
|
||||
$ export ANTHROPIC_API_KEY=<key> # Mac/Linux
|
||||
$ setx ANTHROPIC_API_KEY <key> # Windows, restart shell after setx
|
||||
|
|
|
@ -30,7 +30,7 @@ included for scale.
|
|||
You can code with all of these models using aider like this:
|
||||
|
||||
```
|
||||
$ pip install aider-chat
|
||||
$ python -m pip install aider-chat
|
||||
|
||||
# Change directory into a git repo to work on
|
||||
$ cd /to/your/git/repo
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 158 KiB |
|
@ -38,12 +38,18 @@
|
|||
## Use gpt-4o model for the main chat
|
||||
#4o: false
|
||||
|
||||
## Use gpt-4o-mini model for the main chat
|
||||
#mini: false
|
||||
|
||||
## Use gpt-4-1106-preview model for the main chat
|
||||
#4-turbo: false
|
||||
|
||||
## Use gpt-3.5-turbo model for the main chat
|
||||
#35turbo: false
|
||||
|
||||
## Use deepseek/deepseek-coder model for the main chat
|
||||
#deepseek: false
|
||||
|
||||
#################
|
||||
# Model Settings:
|
||||
|
||||
|
@ -167,8 +173,11 @@
|
|||
## Attribute aider commits in the git committer name (default: True)
|
||||
#attribute-committer: true
|
||||
|
||||
## Prefix commit messages with 'aider: ' (default: False)
|
||||
#attribute-commit-message: false
|
||||
## Prefix commit messages with 'aider: ' if aider authored the changes (default: False)
|
||||
#attribute-commit-message-author: false
|
||||
|
||||
## Prefix all commit messages with 'aider: ' (default: False)
|
||||
#attribute-commit-message-committer: false
|
||||
|
||||
## Commit all pending changes with a suitable commit message, then exit
|
||||
#commit: false
|
||||
|
@ -206,6 +215,9 @@
|
|||
## specify a file to edit (can be used multiple times)
|
||||
#file:
|
||||
|
||||
## specify a read-only file (can be used multiple times)
|
||||
#read:
|
||||
|
||||
## Use VI editing mode in the terminal (default: False)
|
||||
#vim: false
|
||||
|
||||
|
|
|
@ -42,12 +42,18 @@
|
|||
## Use gpt-4o model for the main chat
|
||||
#AIDER_4O=
|
||||
|
||||
## Use gpt-4o-mini model for the main chat
|
||||
#AIDER_MINI=
|
||||
|
||||
## Use gpt-4-1106-preview model for the main chat
|
||||
#AIDER_4_TURBO=
|
||||
|
||||
## Use gpt-3.5-turbo model for the main chat
|
||||
#AIDER_35TURBO=
|
||||
|
||||
## Use deepseek/deepseek-coder model for the main chat
|
||||
#AIDER_DEEPSEEK=
|
||||
|
||||
#################
|
||||
# Model Settings:
|
||||
|
||||
|
@ -171,8 +177,11 @@
|
|||
## Attribute aider commits in the git committer name (default: True)
|
||||
#AIDER_ATTRIBUTE_COMMITTER=true
|
||||
|
||||
## Prefix commit messages with 'aider: ' (default: False)
|
||||
#AIDER_ATTRIBUTE_COMMIT_MESSAGE=false
|
||||
## Prefix commit messages with 'aider: ' if aider authored the changes (default: False)
|
||||
#AIDER_ATTRIBUTE_COMMIT_MESSAGE_AUTHOR=false
|
||||
|
||||
## Prefix all commit messages with 'aider: ' (default: False)
|
||||
#AIDER_ATTRIBUTE_COMMIT_MESSAGE_COMMITTER=false
|
||||
|
||||
## Commit all pending changes with a suitable commit message, then exit
|
||||
#AIDER_COMMIT=false
|
||||
|
@ -210,6 +219,9 @@
|
|||
## specify a file to edit (can be used multiple times)
|
||||
#AIDER_FILE=
|
||||
|
||||
## specify a read-only file (can be used multiple times)
|
||||
#AIDER_READ=
|
||||
|
||||
## Use VI editing mode in the terminal (default: False)
|
||||
#AIDER_VIM=false
|
||||
|
||||
|
|
|
@ -77,12 +77,18 @@ cog.outl("```")
|
|||
## Use gpt-4o model for the main chat
|
||||
#4o: false
|
||||
|
||||
## Use gpt-4o-mini model for the main chat
|
||||
#mini: false
|
||||
|
||||
## Use gpt-4-1106-preview model for the main chat
|
||||
#4-turbo: false
|
||||
|
||||
## Use gpt-3.5-turbo model for the main chat
|
||||
#35turbo: false
|
||||
|
||||
## Use deepseek/deepseek-coder model for the main chat
|
||||
#deepseek: false
|
||||
|
||||
#################
|
||||
# Model Settings:
|
||||
|
||||
|
@ -206,8 +212,11 @@ cog.outl("```")
|
|||
## Attribute aider commits in the git committer name (default: True)
|
||||
#attribute-committer: true
|
||||
|
||||
## Prefix commit messages with 'aider: ' (default: False)
|
||||
#attribute-commit-message: false
|
||||
## Prefix commit messages with 'aider: ' if aider authored the changes (default: False)
|
||||
#attribute-commit-message-author: false
|
||||
|
||||
## Prefix all commit messages with 'aider: ' (default: False)
|
||||
#attribute-commit-message-committer: false
|
||||
|
||||
## Commit all pending changes with a suitable commit message, then exit
|
||||
#commit: false
|
||||
|
@ -245,6 +254,9 @@ cog.outl("```")
|
|||
## specify a file to edit (can be used multiple times)
|
||||
#file:
|
||||
|
||||
## specify a read-only file (can be used multiple times)
|
||||
#read:
|
||||
|
||||
## Use VI editing mode in the terminal (default: False)
|
||||
#vim: false
|
||||
|
||||
|
|
|
@ -84,12 +84,18 @@ cog.outl("```")
|
|||
## Use gpt-4o model for the main chat
|
||||
#AIDER_4O=
|
||||
|
||||
## Use gpt-4o-mini model for the main chat
|
||||
#AIDER_MINI=
|
||||
|
||||
## Use gpt-4-1106-preview model for the main chat
|
||||
#AIDER_4_TURBO=
|
||||
|
||||
## Use gpt-3.5-turbo model for the main chat
|
||||
#AIDER_35TURBO=
|
||||
|
||||
## Use deepseek/deepseek-coder model for the main chat
|
||||
#AIDER_DEEPSEEK=
|
||||
|
||||
#################
|
||||
# Model Settings:
|
||||
|
||||
|
@ -213,8 +219,11 @@ cog.outl("```")
|
|||
## Attribute aider commits in the git committer name (default: True)
|
||||
#AIDER_ATTRIBUTE_COMMITTER=true
|
||||
|
||||
## Prefix commit messages with 'aider: ' (default: False)
|
||||
#AIDER_ATTRIBUTE_COMMIT_MESSAGE=false
|
||||
## Prefix commit messages with 'aider: ' if aider authored the changes (default: False)
|
||||
#AIDER_ATTRIBUTE_COMMIT_MESSAGE_AUTHOR=false
|
||||
|
||||
## Prefix all commit messages with 'aider: ' (default: False)
|
||||
#AIDER_ATTRIBUTE_COMMIT_MESSAGE_COMMITTER=false
|
||||
|
||||
## Commit all pending changes with a suitable commit message, then exit
|
||||
#AIDER_COMMIT=false
|
||||
|
@ -252,6 +261,9 @@ cog.outl("```")
|
|||
## specify a file to edit (can be used multiple times)
|
||||
#AIDER_FILE=
|
||||
|
||||
## specify a read-only file (can be used multiple times)
|
||||
#AIDER_READ=
|
||||
|
||||
## Use VI editing mode in the terminal (default: False)
|
||||
#AIDER_VIM=false
|
||||
|
||||
|
|
|
@ -26,8 +26,8 @@ cog.out(get_md_help())
|
|||
]]]-->
|
||||
```
|
||||
usage: aider [-h] [--openai-api-key] [--anthropic-api-key] [--model]
|
||||
[--opus] [--sonnet] [--4] [--4o] [--4-turbo]
|
||||
[--35turbo] [--models] [--openai-api-base]
|
||||
[--opus] [--sonnet] [--4] [--4o] [--mini] [--4-turbo]
|
||||
[--35turbo] [--deepseek] [--models] [--openai-api-base]
|
||||
[--openai-api-type] [--openai-api-version]
|
||||
[--openai-api-deployment-id] [--openai-organization-id]
|
||||
[--model-settings-file] [--model-metadata-file]
|
||||
|
@ -47,12 +47,13 @@ usage: aider [-h] [--openai-api-key] [--anthropic-api-key] [--model]
|
|||
[--dirty-commits | --no-dirty-commits]
|
||||
[--attribute-author | --no-attribute-author]
|
||||
[--attribute-committer | --no-attribute-committer]
|
||||
[--attribute-commit-message | --no-attribute-commit-message]
|
||||
[--attribute-commit-message-author | --no-attribute-commit-message-author]
|
||||
[--attribute-commit-message-committer | --no-attribute-commit-message-committer]
|
||||
[--commit] [--commit-prompt] [--dry-run | --no-dry-run]
|
||||
[--lint] [--lint-cmd] [--auto-lint | --no-auto-lint]
|
||||
[--test-cmd] [--auto-test | --no-auto-test] [--test]
|
||||
[--file] [--vim] [--voice-language] [--version]
|
||||
[--just-check-update]
|
||||
[--file] [--read] [--vim] [--voice-language]
|
||||
[--version] [--just-check-update]
|
||||
[--check-update | --no-check-update] [--apply] [--yes]
|
||||
[-v] [--show-repo-map] [--show-prompts] [--exit]
|
||||
[--message] [--message-file] [--encoding] [-c] [--gui]
|
||||
|
@ -100,6 +101,10 @@ Aliases:
|
|||
Use gpt-4o model for the main chat
|
||||
Environment variable: `AIDER_4O`
|
||||
|
||||
### `--mini`
|
||||
Use gpt-4o-mini model for the main chat
|
||||
Environment variable: `AIDER_MINI`
|
||||
|
||||
### `--4-turbo`
|
||||
Use gpt-4-1106-preview model for the main chat
|
||||
Environment variable: `AIDER_4_TURBO`
|
||||
|
@ -113,6 +118,10 @@ Aliases:
|
|||
- `--3`
|
||||
- `-3`
|
||||
|
||||
### `--deepseek`
|
||||
Use deepseek/deepseek-coder model for the main chat
|
||||
Environment variable: `AIDER_DEEPSEEK`
|
||||
|
||||
## Model Settings:
|
||||
|
||||
### `--models MODEL`
|
||||
|
@ -160,6 +169,9 @@ Aliases:
|
|||
### `--edit-format EDIT_FORMAT`
|
||||
Specify what edit format the LLM should use (default depends on model)
|
||||
Environment variable: `AIDER_EDIT_FORMAT`
|
||||
Aliases:
|
||||
- `--edit-format EDIT_FORMAT`
|
||||
- `--chat-mode EDIT_FORMAT`
|
||||
|
||||
### `--weak-model WEAK_MODEL`
|
||||
Specify the model to use for commit messages and chat history summarization (default depends on --model)
|
||||
|
@ -327,13 +339,21 @@ Aliases:
|
|||
- `--attribute-committer`
|
||||
- `--no-attribute-committer`
|
||||
|
||||
### `--attribute-commit-message`
|
||||
Prefix commit messages with 'aider: ' (default: False)
|
||||
### `--attribute-commit-message-author`
|
||||
Prefix commit messages with 'aider: ' if aider authored the changes (default: False)
|
||||
Default: False
|
||||
Environment variable: `AIDER_ATTRIBUTE_COMMIT_MESSAGE`
|
||||
Environment variable: `AIDER_ATTRIBUTE_COMMIT_MESSAGE_AUTHOR`
|
||||
Aliases:
|
||||
- `--attribute-commit-message`
|
||||
- `--no-attribute-commit-message`
|
||||
- `--attribute-commit-message-author`
|
||||
- `--no-attribute-commit-message-author`
|
||||
|
||||
### `--attribute-commit-message-committer`
|
||||
Prefix all commit messages with 'aider: ' (default: False)
|
||||
Default: False
|
||||
Environment variable: `AIDER_ATTRIBUTE_COMMIT_MESSAGE_COMMITTER`
|
||||
Aliases:
|
||||
- `--attribute-commit-message-committer`
|
||||
- `--no-attribute-commit-message-committer`
|
||||
|
||||
### `--commit`
|
||||
Commit all pending changes with a suitable commit message, then exit
|
||||
|
@ -396,6 +416,10 @@ Environment variable: `AIDER_TEST`
|
|||
specify a file to edit (can be used multiple times)
|
||||
Environment variable: `AIDER_FILE`
|
||||
|
||||
### `--read FILE`
|
||||
specify a read-only file (can be used multiple times)
|
||||
Environment variable: `AIDER_READ`
|
||||
|
||||
### `--vim`
|
||||
Use VI editing mode in the terminal (default: False)
|
||||
Default: False
|
||||
|
|
|
@ -73,7 +73,7 @@ cd aider
|
|||
# It's recommended to make a virtual environment
|
||||
|
||||
# Install the dependencies listed in the `requirements.txt` file:
|
||||
pip install -e .
|
||||
python -m pip install -e .
|
||||
|
||||
# Run the local version of Aider:
|
||||
python -m aider
|
||||
|
|
|
@ -55,5 +55,9 @@ Aider marks commits that it either authored or committed.
|
|||
You can use `--no-attribute-author` and `--no-attribute-committer` to disable
|
||||
modification of the git author and committer name fields.
|
||||
|
||||
Additionally, you can use `--attribute-commit-message` to prefix commit messages with 'aider: '.
|
||||
This option is disabled by default, but can be useful for easily identifying commits made by aider.
|
||||
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.
|
||||
|
|
|
@ -15,6 +15,8 @@ for more details,
|
|||
or the
|
||||
[usage instructions](https://aider.chat/docs/usage.html) to start coding with aider.
|
||||
|
||||
{% include python-m-aider.md %}
|
||||
|
||||
<div class="video-container">
|
||||
<video controls poster="/assets/install.jpg">
|
||||
<source src="/assets/install.mp4" type="video/mp4">
|
||||
|
|
|
@ -25,13 +25,7 @@ To work with Anthropic's models like Claude 3.5 Sonnet you need a paid
|
|||
[Anthropic API key](https://docs.anthropic.com/claude/reference/getting-started-with-the-api).
|
||||
|
||||
|
||||
{: .tip }
|
||||
Using a Python
|
||||
[virtual environment](https://docs.python.org/3/library/venv.html){:target="_blank"}
|
||||
is recommended.
|
||||
Or, you could
|
||||
[use pipx to install aider](/docs/install/pipx.html)
|
||||
once for your whole system.
|
||||
{% include venv-pipx.md %}
|
||||
|
||||
## Mac/Linux install
|
||||
|
||||
|
@ -59,10 +53,7 @@ $ aider --4o --openai-api-key sk-xxx...
|
|||
$ aider --sonnet --anthropic-api-key sk-xxx...
|
||||
```
|
||||
|
||||
{: .tip }
|
||||
In some environments the `aider` command may not be available
|
||||
on your shell path.
|
||||
You can also run aider like this: `python -m aider`
|
||||
{% include python-m-aider.md %}
|
||||
|
||||
## Working with other LLMs
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ nav_order: 100
|
|||
If you are using aider to work on a python project, sometimes your project will require
|
||||
specific versions of python packages which conflict with the versions that aider
|
||||
requires.
|
||||
If this happens, the `pip install` command may return errors like these:
|
||||
If this happens, the `python -m pip install` command may return errors like these:
|
||||
|
||||
```
|
||||
aider-chat 0.23.0 requires somepackage==X.Y.Z, but you have somepackage U.W.V which is incompatible.
|
||||
|
|
|
@ -5,7 +5,7 @@ description: Aider supports pretty much all popular coding languages.
|
|||
---
|
||||
# Supported languages
|
||||
|
||||
Aider supports almost all popular coding languages.
|
||||
Aider should work well with most popular coding languages.
|
||||
This is because top LLMs are fluent in most mainstream languages,
|
||||
and familiar with popular libraries, packages and frameworks.
|
||||
|
||||
|
@ -20,8 +20,6 @@ a [repository map](https://aider.chat/docs/repomap.html).
|
|||
Aider can currently produce repository maps for many popular
|
||||
mainstream languages, listed below.
|
||||
|
||||
Aider should work quite well for other languages, even those
|
||||
without repo map or linter support.
|
||||
|
||||
<!--[[[cog
|
||||
from aider.repomap import get_supported_languages_md
|
||||
|
@ -82,3 +80,30 @@ cog.out(get_supported_languages_md())
|
|||
|
||||
<!--[[[end]]]-->
|
||||
|
||||
|
||||
|
||||
## How to add support for another language
|
||||
|
||||
Aider should work quite well for other languages, even those
|
||||
without repo map or linter support.
|
||||
You should really try coding with aider before
|
||||
assuming it needs better support for your language.
|
||||
|
||||
That said, if aider already has support for linting your language,
|
||||
then it should be possible to add repo map support.
|
||||
To build a repo map, aider needs the `tags.scm` file
|
||||
from the given language's tree-sitter grammar.
|
||||
If you can find and share that file in a
|
||||
[GitHub issue](https://github.com/paul-gauthier/aider/issues),
|
||||
then it may be possible to add repo map support.
|
||||
|
||||
If aider doesn't support linting, it will be complicated to
|
||||
add linting and repo map support.
|
||||
That is because aider relies on
|
||||
[py-tree-sitter-languages](https://github.com/grantjenks/py-tree-sitter-languages)
|
||||
to provide pre-packaged versions of tree-sitter
|
||||
parsers for many languages.
|
||||
|
||||
Aider needs to be easy for users to install in many environments,
|
||||
and it is probably too complex to add dependencies on
|
||||
additional individual tree-sitter parsers.
|
||||
|
|
|
@ -136,6 +136,16 @@ The model also has to successfully apply all its changes to the source file with
|
|||
tr.selected {
|
||||
color: #0056b3;
|
||||
}
|
||||
table {
|
||||
table-layout: fixed;
|
||||
}
|
||||
td, th {
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
td:nth-child(3), td:nth-child(4) {
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
||||
|
||||
## Code refactoring leaderboard
|
||||
|
@ -291,7 +301,7 @@ Submit results by opening a PR with edits to the
|
|||
By Paul Gauthier,
|
||||
last updated
|
||||
<!--[[[cog
|
||||
import os
|
||||
import subprocess
|
||||
import datetime
|
||||
|
||||
files = [
|
||||
|
@ -300,11 +310,17 @@ files = [
|
|||
'aider/website/_data/refactor_leaderboard.yml'
|
||||
]
|
||||
|
||||
mod_times = [os.path.getmtime(file) for file in files]
|
||||
latest_mod_time = max(mod_times)
|
||||
mod_date = datetime.datetime.fromtimestamp(latest_mod_time)
|
||||
cog.out(f"{mod_date.strftime('%B %d, %Y.')}")
|
||||
def get_last_modified_date(file):
|
||||
result = subprocess.run(['git', 'log', '-1', '--format=%ct', file], capture_output=True, text=True)
|
||||
if result.returncode == 0:
|
||||
timestamp = int(result.stdout.strip())
|
||||
return datetime.datetime.fromtimestamp(timestamp)
|
||||
return datetime.datetime.min
|
||||
|
||||
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.')}")
|
||||
]]]-->
|
||||
August 06, 2024.
|
||||
August 10, 2024.
|
||||
<!--[[[end]]]-->
|
||||
</p>
|
||||
|
|
|
@ -14,7 +14,7 @@ Aider has some built in shortcuts for the most popular Anthropic models and
|
|||
has been tested and benchmarked to work well with them:
|
||||
|
||||
```
|
||||
pip install aider-chat
|
||||
python -m pip install aider-chat
|
||||
|
||||
export ANTHROPIC_API_KEY=<key> # Mac/Linux
|
||||
setx ANTHROPIC_API_KEY <key> # Windows, restart shell after setx
|
||||
|
@ -33,8 +33,8 @@ aider --models anthropic/
|
|||
Anthropic has very low rate limits.
|
||||
You can access all the Anthropic models via
|
||||
[OpenRouter](openrouter.md)
|
||||
without rate limits.
|
||||
For example: `aider --model openrouter/anthropic/claude-3.5-sonnet`
|
||||
or [Google Vertex AI](vertex.md)
|
||||
with more generous rate limits.
|
||||
|
||||
You can use `aider --model <model-name>` to use any other Anthropic model.
|
||||
For example, if you want to use a specific version of Opus
|
||||
|
|
|
@ -8,7 +8,7 @@ nav_order: 500
|
|||
Aider can connect to the OpenAI models on Azure.
|
||||
|
||||
```
|
||||
pip install aider-chat
|
||||
python -m pip install aider-chat
|
||||
|
||||
# Mac/Linux:
|
||||
export AZURE_API_KEY=<key>
|
||||
|
|
|
@ -13,7 +13,7 @@ You'll need a [Cohere API key](https://dashboard.cohere.com/welcome/login).
|
|||
To use **Command-R+**:
|
||||
|
||||
```
|
||||
pip install aider-chat
|
||||
python -m pip install aider-chat
|
||||
|
||||
export COHERE_API_KEY=<key> # Mac/Linux
|
||||
setx COHERE_API_KEY <key> # Windows, restart shell after setx
|
||||
|
|
|
@ -6,19 +6,15 @@ nav_order: 500
|
|||
# DeepSeek
|
||||
|
||||
Aider can connect to the DeepSeek.com API.
|
||||
The DeepSeek Coder V2 model gets the top score on aider's code editing benchmark.
|
||||
The DeepSeek Coder V2 model has a top score on aider's code editing benchmark.
|
||||
|
||||
```
|
||||
pip install aider-chat
|
||||
python -m pip install aider-chat
|
||||
|
||||
export DEEPSEEK_API_KEY=<key> # Mac/Linux
|
||||
setx DEEPSEEK_API_KEY <key> # Windows, restart shell after setx
|
||||
|
||||
# Use DeepSeek Coder V2
|
||||
aider --model deepseek/deepseek-coder
|
||||
aider --deepseek
|
||||
```
|
||||
|
||||
See the [model warnings](warnings.html)
|
||||
section for information on warnings which will occur
|
||||
when working with models that aider is not familiar with.
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ with code editing capability that's comparable to GPT-3.5.
|
|||
You'll need a [Gemini API key](https://aistudio.google.com/app/u/2/apikey).
|
||||
|
||||
```
|
||||
pip install aider-chat
|
||||
python -m pip install aider-chat
|
||||
|
||||
export GEMINI_API_KEY=<key> # Mac/Linux
|
||||
setx GEMINI_API_KEY <key> # Windows, restart shell after setx
|
||||
|
|
|
@ -13,7 +13,7 @@ You'll need a [Groq API key](https://console.groq.com/keys).
|
|||
To use **Llama3 70B**:
|
||||
|
||||
```
|
||||
pip install aider-chat
|
||||
python -m pip install aider-chat
|
||||
|
||||
export GROQ_API_KEY=<key> # Mac/Linux
|
||||
setx GROQ_API_KEY <key> # Windows, restart shell after setx
|
||||
|
|
|
@ -15,7 +15,7 @@ ollama pull <model>
|
|||
ollama serve
|
||||
|
||||
# In another terminal window...
|
||||
pip install aider-chat
|
||||
python -m pip install aider-chat
|
||||
|
||||
export OLLAMA_API_BASE=http://127.0.0.1:11434 # Mac/Linux
|
||||
setx OLLAMA_API_BASE http://127.0.0.1:11434 # Windows, restart shell after setx
|
||||
|
|
|
@ -8,7 +8,7 @@ nav_order: 500
|
|||
Aider can connect to any LLM which is accessible via an OpenAI compatible API endpoint.
|
||||
|
||||
```
|
||||
pip install aider-chat
|
||||
python -m pip install aider-chat
|
||||
|
||||
# Mac/Linux:
|
||||
export OPENAI_API_BASE=<endpoint>
|
||||
|
|
|
@ -14,7 +14,7 @@ Aider has some built in shortcuts for the most popular OpenAI models and
|
|||
has been tested and benchmarked to work well with them:
|
||||
|
||||
```
|
||||
pip install aider-chat
|
||||
python -m pip install aider-chat
|
||||
|
||||
export OPENAI_API_KEY=<key> # Mac/Linux
|
||||
setx OPENAI_API_KEY <key> # Windows, restart shell after setx
|
||||
|
|
|
@ -9,7 +9,7 @@ Aider can connect to [models provided by OpenRouter](https://openrouter.ai/model
|
|||
You'll need an [OpenRouter API key](https://openrouter.ai/keys).
|
||||
|
||||
```
|
||||
pip install aider-chat
|
||||
python -m pip install aider-chat
|
||||
|
||||
export OPENROUTER_API_KEY=<key> # Mac/Linux
|
||||
setx OPENROUTER_API_KEY <key> # Windows, restart shell after setx
|
||||
|
@ -21,15 +21,15 @@ aider --model openrouter/<provider>/<model>
|
|||
aider --models openrouter/
|
||||
```
|
||||
|
||||
In particular, Llama3 70B works well with aider, at low cost:
|
||||
In particular, many aider users access Sonnet via OpenRouter:
|
||||
|
||||
```
|
||||
pip install aider-chat
|
||||
python -m pip install aider-chat
|
||||
|
||||
export OPENROUTER_API_KEY=<key> # Mac/Linux
|
||||
setx OPENROUTER_API_KEY <key> # Windows, restart shell after setx
|
||||
|
||||
aider --model openrouter/meta-llama/llama-3-70b-instruct
|
||||
aider --model openrouter/anthropic/claude-3.5-sonnet
|
||||
```
|
||||
|
||||
|
||||
|
|
|
@ -77,6 +77,7 @@ cog.out(''.join(lines))
|
|||
- FIREWORKS_API_KEY
|
||||
- FRIENDLIAI_API_KEY
|
||||
- GEMINI_API_KEY
|
||||
- GITHUB_API_KEY
|
||||
- GROQ_API_KEY
|
||||
- HUGGINGFACE_API_KEY
|
||||
- MARITALK_API_KEY
|
||||
|
|
43
aider/website/docs/llms/vertex.md
Normal file
43
aider/website/docs/llms/vertex.md
Normal file
|
@ -0,0 +1,43 @@
|
|||
---
|
||||
parent: Connecting to LLMs
|
||||
nav_order: 550
|
||||
---
|
||||
|
||||
# Vertex AI
|
||||
|
||||
Aider can connect to models provided by Google Vertex AI.
|
||||
You will need to install the
|
||||
[gcloud CLI](https://cloud.google.com/sdk/docs/install) and [login](https://cloud.google.com/sdk/docs/initializing) with a GCP account
|
||||
or service account with permission to use the Vertex AI API.
|
||||
|
||||
With your chosen login method, the gcloud CLI should automatically set the
|
||||
`GOOGLE_APPLICATION_CREDENTIALS` environment variable which points to the credentials file.
|
||||
|
||||
To configure Aider to use the Vertex AI API, you need to set `VERTEXAI_PROJECT` (the GCP project ID)
|
||||
and `VERTEXAI_LOCATION` (the GCP region) [environment variables for Aider](/docs/config/dotenv.html).
|
||||
|
||||
Note that Claude on Vertex AI is only available in certain GCP regions,
|
||||
check [the model card](https://console.cloud.google.com/vertex-ai/publishers/anthropic/model-garden/claude-3-5-sonnet)
|
||||
for your model to see which regions are supported.
|
||||
|
||||
Example `.env` file:
|
||||
|
||||
```
|
||||
VERTEXAI_PROJECT=my-project
|
||||
VERTEXAI_LOCATION=us-east5
|
||||
```
|
||||
|
||||
Then you can run aider with the `--model` command line switch, like this:
|
||||
|
||||
```
|
||||
aider --model vertex_ai/claude-3-5-sonnet@20240620
|
||||
```
|
||||
|
||||
Or you can use the [yaml config](/docs/config/aider_conf.html) to set the model to any of the
|
||||
models supported by Vertex AI.
|
||||
|
||||
Example `.aider.conf.yml` file:
|
||||
|
||||
```yaml
|
||||
model: vertex_ai/claude-3-5-sonnet@20240620
|
||||
```
|
|
@ -74,6 +74,10 @@ coder.run("make a script that prints hello world")
|
|||
|
||||
# Send another instruction
|
||||
coder.run("make it say goodbye")
|
||||
|
||||
# You can run in-chat "/" commands too
|
||||
coder.run("/tokens")
|
||||
|
||||
```
|
||||
|
||||
See the
|
||||
|
|
25
aider/website/docs/troubleshooting/aider-not-found.md
Normal file
25
aider/website/docs/troubleshooting/aider-not-found.md
Normal file
|
@ -0,0 +1,25 @@
|
|||
---
|
||||
parent: Troubleshooting
|
||||
nav_order: 28
|
||||
---
|
||||
|
||||
# Aider not found
|
||||
|
||||
In some environments the `aider` command may not be available
|
||||
on your shell path.
|
||||
This can occur because of permissions/security settings in your OS,
|
||||
and often happens to Windows users.
|
||||
|
||||
You may see an error message like this:
|
||||
|
||||
> aider: The term 'aider' is not recognized as a name of a cmdlet, function, script file, or executable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
|
||||
|
||||
Below is the most fail safe way to install and run aider in these situations:
|
||||
|
||||
```
|
||||
python -m pip install aider-chat
|
||||
python -m aider
|
||||
```
|
||||
|
||||
|
||||
{% include venv-pipx.md %}
|
|
@ -48,7 +48,7 @@ It also supports [connecting to almost any LLM](https://aider.chat/docs/llms.htm
|
|||
Use the `--browser` switch to launch the browser version of aider:
|
||||
|
||||
```
|
||||
pip install aider-chat
|
||||
python -m pip install aider-chat
|
||||
|
||||
export OPENAI_API_KEY=<key> # Mac/Linux
|
||||
setx OPENAI_API_KEY <key> # Windows, restart shell after setx
|
||||
|
|
|
@ -15,27 +15,30 @@ cog.out(get_help_md())
|
|||
|Command|Description|
|
||||
|:------|:----------|
|
||||
| **/add** | Add files to the chat so GPT can edit them or review them in detail |
|
||||
| **/add-clipboard-image** | Add an image from the clipboard to the chat |
|
||||
| **/ask** | Ask questions about the code base without editing any files |
|
||||
| **/chat-mode** | Switch to a new chat mode |
|
||||
| **/clear** | Clear the chat history |
|
||||
| **/clipboard** | Add image/text from the clipboard to the chat (optionally provide a name for the image) |
|
||||
| **/code** | Ask for changes to your code |
|
||||
| **/commit** | Commit edits to the repo made outside the chat (commit message optional) |
|
||||
| **/diff** | Display the diff of the last aider commit |
|
||||
| **/diff** | Display the diff of changes since the last message |
|
||||
| **/drop** | Remove files from the chat session to free up context space |
|
||||
| **/exit** | Exit the application |
|
||||
| **/git** | Run a git command |
|
||||
| **/help** | Ask questions about aider |
|
||||
| **/lint** | Lint and fix provided files or in-chat files if none provided |
|
||||
| **/ls** | List all known files and indicate which are included in the chat session |
|
||||
| **/map** | Print out the current repository map |
|
||||
| **/model** | Switch to a new LLM |
|
||||
| **/models** | Search the list of available models |
|
||||
| **/quit** | Exit the application |
|
||||
| **/read** | Add a file to the chat that is for reference, not to be edited |
|
||||
| **/run** | Run a shell command and optionally add the output to the chat (alias: !) |
|
||||
| **/test** | Run a shell command and add the output to the chat on non-zero exit code |
|
||||
| **/tokens** | Report on the number of tokens used by the current chat context |
|
||||
| **/undo** | Undo the last git commit if it was done by aider |
|
||||
| **/voice** | Record and transcribe voice input |
|
||||
| **/web** | Use headless selenium to scrape a webpage and add the content to the chat |
|
||||
| **/web** | Scrape a webpage, convert to markdown and add to the chat |
|
||||
|
||||
<!--[[[end]]]-->
|
||||
|
||||
|
|
|
@ -13,7 +13,20 @@ Aider has 3 different chat modes:
|
|||
- `help` - Aider will answer questions about using aider, configuring, troubleshooting, etc.
|
||||
|
||||
By default, aider starts in "code" mode. As you are talking, you can
|
||||
send messages in other modes using `/ask` and `/help` commands:
|
||||
send individual messages in a specific mode using `/code`, `/ask`, and `/help` commands:
|
||||
Using `/code`, `/ask`, and `/help` applies just to that particular message.
|
||||
Your next message will go back to the active mode (usually "code" mode by default).
|
||||
|
||||
You can switch the active mode in a sticky way
|
||||
with the `/chat-mode <mode>` command:
|
||||
|
||||
```
|
||||
/chat-mode code
|
||||
/chat-mode ask
|
||||
/chat-mode help
|
||||
```
|
||||
|
||||
Or you can launch aider in one of the modes with the `--chat-mode <mode>` switch.
|
||||
|
||||
<div class="chat-transcript" markdown="1">
|
||||
|
||||
|
@ -33,17 +46,22 @@ This is the source code to the popular django package.
|
|||
Run `aider --model ollama/<ollama-model>`.
|
||||
See these docs for more info: https://aider.chat/docs/llms/ollama.html
|
||||
|
||||
#### /code Add a function to calculate the factorial of a number
|
||||
|
||||
Certainly! I'll add a function to calculate the factorial of a number. Here's the change:
|
||||
|
||||
```python
|
||||
def factorial(n):
|
||||
if n == 0 or n == 1:
|
||||
return 1
|
||||
else:
|
||||
return n * factorial(n - 1)
|
||||
|
||||
# Example usage
|
||||
print(factorial(5)) # Output: 120
|
||||
```
|
||||
|
||||
This function uses recursion to calculate the factorial. It's been added to your code.
|
||||
|
||||
</div>
|
||||
|
||||
Using `/ask` and `/help` applies just to that particular message.
|
||||
Your next message will go back to code mode.
|
||||
|
||||
You can switch between the modes in a sticky way
|
||||
with the `/chat-mode <mode>` command:
|
||||
|
||||
```
|
||||
/chat-mode code
|
||||
/chat-mode ask
|
||||
/chat-mode help
|
||||
```
|
||||
|
||||
|
|
|
@ -49,18 +49,18 @@ cog.out(open("aider/website/_includes/get-started.md").read())
|
|||
You can get started quickly like this:
|
||||
|
||||
```
|
||||
$ pip install aider-chat
|
||||
python -m pip install aider-chat
|
||||
|
||||
# Change directory into a git repo
|
||||
$ cd /to/your/git/repo
|
||||
cd /to/your/git/repo
|
||||
|
||||
# Work with Claude 3.5 Sonnet on your repo
|
||||
$ export ANTHROPIC_API_KEY=your-key-goes-here
|
||||
$ aider
|
||||
export ANTHROPIC_API_KEY=your-key-goes-here
|
||||
aider
|
||||
|
||||
# Work with GPT-4o on your repo
|
||||
$ export OPENAI_API_KEY=your-key-goes-here
|
||||
$ aider
|
||||
export OPENAI_API_KEY=your-key-goes-here
|
||||
aider
|
||||
```
|
||||
<!-- NOOP -->
|
||||
|
||||
|
|
67
pyproject.toml
Normal file
67
pyproject.toml
Normal file
|
@ -0,0 +1,67 @@
|
|||
# [[[cog
|
||||
# from aider.help_pats import exclude_website_pats
|
||||
# ]]]
|
||||
# [[[end]]]
|
||||
|
||||
[project]
|
||||
name = "aider-chat"
|
||||
description = "Aider is AI pair programming in your terminal"
|
||||
readme = "README.md"
|
||||
classifiers = [
|
||||
"Development Status :: 4 - Beta",
|
||||
"Environment :: Console",
|
||||
"Intended Audience :: Developers",
|
||||
"License :: OSI Approved :: Apache Software License",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
"Programming Language :: Python",
|
||||
"Topic :: Software Development",
|
||||
]
|
||||
requires-python = ">=3.9,<3.13"
|
||||
dynamic = ["dependencies", "optional-dependencies", "version"]
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://github.com/paul-gauthier/aider"
|
||||
|
||||
[project.scripts]
|
||||
aider = "aider.main:main"
|
||||
|
||||
[tool.setuptools.dynamic]
|
||||
version = { attr = "aider.__init__.__version__" }
|
||||
dependencies = { file = "requirements.txt" }
|
||||
|
||||
[tool.setuptools.dynamic.optional-dependencies]
|
||||
dev = { file = "requirements/requirements-dev.txt" }
|
||||
help = { file = "requirements/requirements-help.txt" }
|
||||
browser = { file = "requirements/requirements-browser.txt" }
|
||||
playwright = { file = "requirements/requirements-playwright.txt" }
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
include = ["aider*", "aider.website"]
|
||||
|
||||
[tool.setuptools.package-data]
|
||||
"aider" = ["queries/*.scm"]
|
||||
"aider.website" = ["**/*.md"]
|
||||
|
||||
[tool.setuptools.exclude-package-data]
|
||||
"aider.website" = [
|
||||
# [[[cog
|
||||
# cog.out("\n".join(f' "{pat}",' for pat in exclude_website_pats))
|
||||
# ]]]
|
||||
"examples/**",
|
||||
"_posts/**",
|
||||
"HISTORY.md",
|
||||
"docs/benchmarks*md",
|
||||
"docs/ctags.md",
|
||||
"docs/unified-diffs.md",
|
||||
"docs/leaderboards/index.md",
|
||||
"assets/**",
|
||||
# [[[end]]]
|
||||
]
|
||||
|
||||
[build-system]
|
||||
requires = ["setuptools>=68"]
|
||||
build-backend = "setuptools.build_meta"
|
|
@ -4,9 +4,9 @@
|
|||
#
|
||||
# pip-compile --output-file=requirements.txt requirements/requirements.in
|
||||
#
|
||||
aiohappyeyeballs==2.3.4
|
||||
aiohappyeyeballs==2.3.5
|
||||
# via aiohttp
|
||||
aiohttp==3.10.0
|
||||
aiohttp==3.10.3
|
||||
# via litellm
|
||||
aiosignal==1.3.1
|
||||
# via aiohttp
|
||||
|
@ -16,7 +16,7 @@ anyio==4.4.0
|
|||
# via
|
||||
# httpx
|
||||
# openai
|
||||
attrs==23.2.0
|
||||
attrs==24.2.0
|
||||
# via
|
||||
# aiohttp
|
||||
# jsonschema
|
||||
|
@ -30,7 +30,7 @@ certifi==2024.7.4
|
|||
# httpcore
|
||||
# httpx
|
||||
# requests
|
||||
cffi==1.16.0
|
||||
cffi==1.17.0
|
||||
# via
|
||||
# sounddevice
|
||||
# soundfile
|
||||
|
@ -48,7 +48,7 @@ distro==1.9.0
|
|||
# via openai
|
||||
filelock==3.15.4
|
||||
# via huggingface-hub
|
||||
flake8==7.1.0
|
||||
flake8==7.1.1
|
||||
# via -r requirements/requirements.in
|
||||
frozenlist==1.4.1
|
||||
# via
|
||||
|
@ -84,13 +84,15 @@ importlib-resources==6.4.0
|
|||
# via -r requirements/requirements.in
|
||||
jinja2==3.1.4
|
||||
# via litellm
|
||||
jiter==0.5.0
|
||||
# via openai
|
||||
jsonschema==4.23.0
|
||||
# via
|
||||
# -r requirements/requirements.in
|
||||
# litellm
|
||||
jsonschema-specifications==2023.12.1
|
||||
# via jsonschema
|
||||
litellm==1.42.9
|
||||
litellm==1.43.9
|
||||
# via -r requirements/requirements.in
|
||||
markdown-it-py==3.0.0
|
||||
# via rich
|
||||
|
@ -110,7 +112,7 @@ numpy==1.26.4
|
|||
# via
|
||||
# -r requirements/requirements.in
|
||||
# scipy
|
||||
openai==1.37.2
|
||||
openai==1.40.6
|
||||
# via litellm
|
||||
packaging==24.1
|
||||
# via
|
||||
|
@ -123,8 +125,10 @@ pathspec==0.12.1
|
|||
pillow==10.4.0
|
||||
# via -r requirements/requirements.in
|
||||
prompt-toolkit==3.0.47
|
||||
# via -r requirements/requirements.in
|
||||
pycodestyle==2.12.0
|
||||
# via
|
||||
# -r requirements/requirements.in
|
||||
# pypager
|
||||
pycodestyle==2.12.1
|
||||
# via flake8
|
||||
pycparser==2.22
|
||||
# via cffi
|
||||
|
@ -137,12 +141,18 @@ pydantic-core==2.20.1
|
|||
pyflakes==3.2.0
|
||||
# via flake8
|
||||
pygments==2.18.0
|
||||
# via rich
|
||||
# via
|
||||
# pypager
|
||||
# rich
|
||||
pypager==3.0.1
|
||||
# via -r requirements/requirements.in
|
||||
pypandoc==1.13
|
||||
# via -r requirements/requirements.in
|
||||
pyperclip==1.9.0
|
||||
# via -r requirements/requirements.in
|
||||
python-dotenv==1.0.1
|
||||
# via litellm
|
||||
pyyaml==6.0.1
|
||||
pyyaml==6.0.2
|
||||
# via
|
||||
# -r requirements/requirements.in
|
||||
# huggingface-hub
|
||||
|
@ -159,7 +169,7 @@ requests==2.32.3
|
|||
# tiktoken
|
||||
rich==13.7.1
|
||||
# via -r requirements/requirements.in
|
||||
rpds-py==0.19.1
|
||||
rpds-py==0.20.0
|
||||
# via
|
||||
# jsonschema
|
||||
# referencing
|
||||
|
@ -172,7 +182,7 @@ sniffio==1.3.1
|
|||
# anyio
|
||||
# httpx
|
||||
# openai
|
||||
sounddevice==0.4.7
|
||||
sounddevice==0.5.0
|
||||
# via -r requirements/requirements.in
|
||||
soundfile==0.12.1
|
||||
# via -r requirements/requirements.in
|
||||
|
@ -181,8 +191,10 @@ soupsieve==2.5
|
|||
tiktoken==0.7.0
|
||||
# via litellm
|
||||
tokenizers==0.19.1
|
||||
# via litellm
|
||||
tqdm==4.66.4
|
||||
# via
|
||||
# -r requirements/requirements.in
|
||||
# litellm
|
||||
tqdm==4.66.5
|
||||
# via
|
||||
# huggingface-hub
|
||||
# openai
|
||||
|
@ -204,5 +216,5 @@ wcwidth==0.2.13
|
|||
# via prompt-toolkit
|
||||
yarl==1.9.4
|
||||
# via aiohttp
|
||||
zipp==3.19.2
|
||||
zipp==3.20.0
|
||||
# via importlib-metadata
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
#
|
||||
# pip-compile --output-file=requirements/requirements-browser.txt requirements/requirements-browser.in
|
||||
#
|
||||
altair==5.3.0
|
||||
altair==5.4.0
|
||||
# via streamlit
|
||||
attrs==23.2.0
|
||||
attrs==24.2.0
|
||||
# via
|
||||
# -c requirements/../requirements.txt
|
||||
# jsonschema
|
||||
|
@ -64,10 +64,11 @@ mdurl==0.1.2
|
|||
# via
|
||||
# -c requirements/../requirements.txt
|
||||
# markdown-it-py
|
||||
narwhals==1.3.0
|
||||
# via altair
|
||||
numpy==1.26.4
|
||||
# via
|
||||
# -c requirements/../requirements.txt
|
||||
# altair
|
||||
# pandas
|
||||
# pyarrow
|
||||
# pydeck
|
||||
|
@ -78,9 +79,7 @@ packaging==24.1
|
|||
# altair
|
||||
# streamlit
|
||||
pandas==2.2.2
|
||||
# via
|
||||
# altair
|
||||
# streamlit
|
||||
# via streamlit
|
||||
pillow==10.4.0
|
||||
# via
|
||||
# -c requirements/../requirements.txt
|
||||
|
@ -112,7 +111,7 @@ rich==13.7.1
|
|||
# via
|
||||
# -c requirements/../requirements.txt
|
||||
# streamlit
|
||||
rpds-py==0.19.1
|
||||
rpds-py==0.20.0
|
||||
# via
|
||||
# -c requirements/../requirements.txt
|
||||
# jsonschema
|
||||
|
@ -123,19 +122,18 @@ smmap==5.0.1
|
|||
# via
|
||||
# -c requirements/../requirements.txt
|
||||
# gitdb
|
||||
streamlit==1.37.0
|
||||
streamlit==1.37.1
|
||||
# via -r requirements/requirements-browser.in
|
||||
tenacity==8.5.0
|
||||
# via streamlit
|
||||
toml==0.10.2
|
||||
# via streamlit
|
||||
toolz==0.12.1
|
||||
# via altair
|
||||
tornado==6.4.1
|
||||
# via streamlit
|
||||
typing-extensions==4.12.2
|
||||
# via
|
||||
# -c requirements/../requirements.txt
|
||||
# altair
|
||||
# streamlit
|
||||
tzdata==2024.1
|
||||
# via pandas
|
||||
|
@ -143,5 +141,5 @@ urllib3==2.2.2
|
|||
# via
|
||||
# -c requirements/../requirements.txt
|
||||
# requests
|
||||
watchdog==4.0.1
|
||||
watchdog==4.0.2
|
||||
# via -r requirements/requirements-browser.in
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
#
|
||||
alabaster==0.7.16
|
||||
# via sphinx
|
||||
babel==2.15.0
|
||||
babel==2.16.0
|
||||
# via sphinx
|
||||
build==1.2.1
|
||||
# via pip-tools
|
||||
|
@ -75,7 +75,7 @@ markupsafe==2.1.5
|
|||
# via
|
||||
# -c requirements/../requirements.txt
|
||||
# jinja2
|
||||
matplotlib==3.9.1
|
||||
matplotlib==3.9.2
|
||||
# via -r requirements/requirements-dev.in
|
||||
mdurl==0.1.2
|
||||
# via
|
||||
|
@ -137,7 +137,7 @@ python-dateutil==2.9.0.post0
|
|||
# pandas
|
||||
pytz==2024.1
|
||||
# via pandas
|
||||
pyyaml==6.0.1
|
||||
pyyaml==6.0.2
|
||||
# via
|
||||
# -c requirements/../requirements.txt
|
||||
# pre-commit
|
||||
|
@ -191,7 +191,7 @@ urllib3==2.2.2
|
|||
# requests
|
||||
virtualenv==20.26.3
|
||||
# via pre-commit
|
||||
wheel==0.43.0
|
||||
wheel==0.44.0
|
||||
# via pip-tools
|
||||
|
||||
# The following packages are considered to be unsafe in a requirements file:
|
||||
|
|
|
@ -4,11 +4,11 @@
|
|||
#
|
||||
# pip-compile --output-file=requirements/requirements-help.txt requirements/requirements-help.in
|
||||
#
|
||||
aiohappyeyeballs==2.3.4
|
||||
aiohappyeyeballs==2.3.5
|
||||
# via
|
||||
# -c requirements/../requirements.txt
|
||||
# aiohttp
|
||||
aiohttp==3.10.0
|
||||
aiohttp==3.10.3
|
||||
# via
|
||||
# -c requirements/../requirements.txt
|
||||
# huggingface-hub
|
||||
|
@ -26,7 +26,7 @@ anyio==4.4.0
|
|||
# -c requirements/../requirements.txt
|
||||
# httpx
|
||||
# openai
|
||||
attrs==23.2.0
|
||||
attrs==24.2.0
|
||||
# via
|
||||
# -c requirements/../requirements.txt
|
||||
# aiohttp
|
||||
|
@ -104,15 +104,19 @@ jinja2==3.1.4
|
|||
# via
|
||||
# -c requirements/../requirements.txt
|
||||
# torch
|
||||
jiter==0.5.0
|
||||
# via
|
||||
# -c requirements/../requirements.txt
|
||||
# openai
|
||||
joblib==1.4.2
|
||||
# via
|
||||
# nltk
|
||||
# scikit-learn
|
||||
llama-index-core==0.10.59
|
||||
llama-index-core==0.10.65
|
||||
# via
|
||||
# -r requirements/requirements-help.in
|
||||
# llama-index-embeddings-huggingface
|
||||
llama-index-embeddings-huggingface==0.2.2
|
||||
llama-index-embeddings-huggingface==0.2.3
|
||||
# via -r requirements/requirements-help.in
|
||||
markupsafe==2.1.5
|
||||
# via
|
||||
|
@ -138,7 +142,7 @@ networkx==3.2.1
|
|||
# -c requirements/../requirements.txt
|
||||
# llama-index-core
|
||||
# torch
|
||||
nltk==3.8.1
|
||||
nltk==3.8.2
|
||||
# via llama-index-core
|
||||
numpy==1.26.4
|
||||
# via
|
||||
|
@ -149,7 +153,7 @@ numpy==1.26.4
|
|||
# scipy
|
||||
# sentence-transformers
|
||||
# transformers
|
||||
openai==1.37.2
|
||||
openai==1.40.6
|
||||
# via
|
||||
# -c requirements/../requirements.txt
|
||||
# llama-index-core
|
||||
|
@ -178,7 +182,7 @@ python-dateutil==2.9.0.post0
|
|||
# via pandas
|
||||
pytz==2024.1
|
||||
# via pandas
|
||||
pyyaml==6.0.1
|
||||
pyyaml==6.0.2
|
||||
# via
|
||||
# -c requirements/../requirements.txt
|
||||
# huggingface-hub
|
||||
|
@ -197,7 +201,7 @@ requests==2.32.3
|
|||
# llama-index-core
|
||||
# tiktoken
|
||||
# transformers
|
||||
safetensors==0.4.3
|
||||
safetensors==0.4.4
|
||||
# via transformers
|
||||
scikit-learn==1.5.1
|
||||
# via sentence-transformers
|
||||
|
@ -216,11 +220,11 @@ sniffio==1.3.1
|
|||
# anyio
|
||||
# httpx
|
||||
# openai
|
||||
sqlalchemy[asyncio]==2.0.31
|
||||
sqlalchemy[asyncio]==2.0.32
|
||||
# via
|
||||
# llama-index-core
|
||||
# sqlalchemy
|
||||
sympy==1.13.1
|
||||
sympy==1.13.2
|
||||
# via torch
|
||||
tenacity==8.5.0
|
||||
# via llama-index-core
|
||||
|
@ -236,7 +240,7 @@ tokenizers==0.19.1
|
|||
# transformers
|
||||
torch==2.2.2
|
||||
# via sentence-transformers
|
||||
tqdm==4.66.4
|
||||
tqdm==4.66.5
|
||||
# via
|
||||
# -c requirements/../requirements.txt
|
||||
# huggingface-hub
|
||||
|
@ -245,7 +249,7 @@ tqdm==4.66.4
|
|||
# openai
|
||||
# sentence-transformers
|
||||
# transformers
|
||||
transformers==4.43.3
|
||||
transformers==4.44.0
|
||||
# via sentence-transformers
|
||||
typing-extensions==4.12.2
|
||||
# via
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
#
|
||||
greenlet==3.0.3
|
||||
# via playwright
|
||||
playwright==1.45.1
|
||||
playwright==1.46.0
|
||||
# via -r requirements/requirements-playwright.in
|
||||
pyee==11.1.0
|
||||
# via playwright
|
||||
|
|
|
@ -22,6 +22,8 @@ pypandoc
|
|||
litellm
|
||||
flake8
|
||||
importlib_resources
|
||||
pyperclip
|
||||
pypager
|
||||
|
||||
# The proper depdendency is networkx[default], but this brings
|
||||
# in matplotlib and a bunch of other deps
|
||||
|
@ -46,3 +48,7 @@ importlib-metadata<8.0.0
|
|||
|
||||
# Because sentence-transformers doesn't like >=2
|
||||
numpy<2
|
||||
|
||||
# Going past this makes dependencies unresolvable
|
||||
# Seems to be caused by sentence-transformers
|
||||
tokenizers==0.19.1
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import argparse
|
||||
import subprocess
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from datetime import datetime
|
||||
from operator import itemgetter
|
||||
|
@ -17,10 +18,14 @@ def blame(start_tag, end_tag=None):
|
|||
|
||||
authors = get_commit_authors(commits)
|
||||
|
||||
pats = "*.py *.scm *.sh **Dockerfile **Gemfile .github/workflows/*.yml".split()
|
||||
files = []
|
||||
for pat in pats:
|
||||
files += run(["git", "ls-files", pat]).strip().split("\n")
|
||||
revision = end_tag if end_tag else "HEAD"
|
||||
files = run(["git", "ls-tree", "-r", "--name-only", revision]).strip().split("\n")
|
||||
files = [
|
||||
f
|
||||
for f in files
|
||||
if f.endswith((".py", ".scm", ".sh", "Dockerfile", "Gemfile"))
|
||||
or (f.startswith(".github/workflows/") and f.endswith(".yml"))
|
||||
]
|
||||
|
||||
all_file_counts = {}
|
||||
grand_total = defaultdict(int)
|
||||
|
@ -186,9 +191,13 @@ def get_counts_for_file(start_tag, end_tag, authors, fname):
|
|||
line_counts[author] += 1
|
||||
|
||||
return dict(line_counts)
|
||||
except subprocess.CalledProcessError:
|
||||
# print(f"Warning: Unable to blame file {fname}. It may have been added after {start_tag} "
|
||||
# f"or removed before {end_tag or 'HEAD'}.", file=sys.stderr)
|
||||
except subprocess.CalledProcessError as e:
|
||||
if "no such path" in str(e).lower():
|
||||
# File doesn't exist in this revision range, which is okay
|
||||
return None
|
||||
else:
|
||||
# Some other error occurred
|
||||
print(f"Warning: Unable to blame file {fname}. Error: {e}", file=sys.stderr)
|
||||
return None
|
||||
|
||||
|
||||
|
|
|
@ -9,22 +9,36 @@ import sys
|
|||
from packaging import version
|
||||
|
||||
|
||||
def check_cog_pyproject():
|
||||
result = subprocess.run(["cog", "--check", "pyproject.toml"], capture_output=True, text=True)
|
||||
|
||||
if result.returncode != 0:
|
||||
print("Error: cog --check pyproject.toml failed, updating.")
|
||||
subprocess.run(["cog", "-r", "pyproject.toml"])
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Bump version")
|
||||
parser.add_argument("new_version", help="New version in x.y.z format")
|
||||
parser.add_argument(
|
||||
"--dry-run", action="store_true", help="Print each step without actually executing them"
|
||||
)
|
||||
|
||||
# Function to check if we are on the main branch
|
||||
def check_branch():
|
||||
branch = subprocess.run(["git", "rev-parse", "--abbrev-ref", "HEAD"], capture_output=True, text=True).stdout.strip()
|
||||
branch = subprocess.run(
|
||||
["git", "rev-parse", "--abbrev-ref", "HEAD"], capture_output=True, text=True
|
||||
).stdout.strip()
|
||||
if branch != "main":
|
||||
print("Error: Not on the main branch.")
|
||||
sys.exit(1)
|
||||
|
||||
# Function to check if the working directory is clean
|
||||
def check_working_directory_clean():
|
||||
status = subprocess.run(["git", "status", "--porcelain"], capture_output=True, text=True).stdout
|
||||
status = subprocess.run(
|
||||
["git", "status", "--porcelain"], capture_output=True, text=True
|
||||
).stdout
|
||||
if status:
|
||||
print("Error: Working directory is not clean.")
|
||||
sys.exit(1)
|
||||
|
@ -32,19 +46,33 @@ def main():
|
|||
# Function to fetch the latest changes and check if the main branch is up to date
|
||||
def check_main_branch_up_to_date():
|
||||
subprocess.run(["git", "fetch", "origin"], check=True)
|
||||
local_main = subprocess.run(["git", "rev-parse", "main"], capture_output=True, text=True).stdout.strip()
|
||||
local_main = subprocess.run(
|
||||
["git", "rev-parse", "main"], capture_output=True, text=True
|
||||
).stdout.strip()
|
||||
print(f"Local main commit hash: {local_main}")
|
||||
origin_main = subprocess.run(["git", "rev-parse", "origin/main"], capture_output=True, text=True).stdout.strip()
|
||||
origin_main = subprocess.run(
|
||||
["git", "rev-parse", "origin/main"], capture_output=True, text=True
|
||||
).stdout.strip()
|
||||
print(f"Origin main commit hash: {origin_main}")
|
||||
if local_main != origin_main:
|
||||
local_date = subprocess.run(["git", "show", "-s", "--format=%ci", "main"], capture_output=True, text=True).stdout.strip()
|
||||
origin_date = subprocess.run(["git", "show", "-s", "--format=%ci", "origin/main"], capture_output=True, text=True).stdout.strip()
|
||||
local_date = subprocess.run(
|
||||
["git", "show", "-s", "--format=%ci", "main"], capture_output=True, text=True
|
||||
).stdout.strip()
|
||||
origin_date = subprocess.run(
|
||||
["git", "show", "-s", "--format=%ci", "origin/main"], capture_output=True, text=True
|
||||
).stdout.strip()
|
||||
local_date = datetime.datetime.strptime(local_date, "%Y-%m-%d %H:%M:%S %z")
|
||||
origin_date = datetime.datetime.strptime(origin_date, "%Y-%m-%d %H:%M:%S %z")
|
||||
if local_date < origin_date:
|
||||
print("Error: The local main branch is behind origin/main. Please pull the latest changes.")
|
||||
print(
|
||||
"Error: The local main branch is behind origin/main. Please pull the latest"
|
||||
" changes."
|
||||
)
|
||||
elif local_date > origin_date:
|
||||
print("Error: The origin/main branch is behind the local main branch. Please push your changes.")
|
||||
print(
|
||||
"Error: The origin/main branch is behind the local main branch. Please push"
|
||||
" your changes."
|
||||
)
|
||||
else:
|
||||
print("Error: The main branch and origin/main have diverged.")
|
||||
sys.exit(1)
|
||||
|
@ -53,6 +81,7 @@ def main():
|
|||
dry_run = args.dry_run
|
||||
|
||||
# Perform checks before proceeding
|
||||
check_cog_pyproject()
|
||||
check_branch()
|
||||
check_working_directory_clean()
|
||||
check_main_branch_up_to_date()
|
||||
|
|
73
setup.py
73
setup.py
|
@ -1,73 +0,0 @@
|
|||
import re
|
||||
from pathlib import Path
|
||||
|
||||
from setuptools import find_packages, setup
|
||||
|
||||
from aider import __version__
|
||||
from aider.help_pats import exclude_website_pats
|
||||
|
||||
|
||||
def get_requirements(suffix=""):
|
||||
if suffix:
|
||||
fname = "requirements-" + suffix + ".txt"
|
||||
fname = Path("requirements") / fname
|
||||
else:
|
||||
fname = Path("requirements.txt")
|
||||
|
||||
requirements = fname.read_text().splitlines()
|
||||
|
||||
return requirements
|
||||
|
||||
|
||||
requirements = get_requirements()
|
||||
|
||||
# README
|
||||
with open("README.md", "r", encoding="utf-8") as f:
|
||||
long_description = f.read()
|
||||
long_description = re.sub(r"\n!\[.*\]\(.*\)", "", long_description)
|
||||
# long_description = re.sub(r"\n- \[.*\]\(.*\)", "", long_description)
|
||||
|
||||
# Discover packages, plus the website
|
||||
packages = find_packages(exclude=["benchmark", "tests"])
|
||||
packages += ["aider.website"]
|
||||
|
||||
print("Packages:", packages)
|
||||
|
||||
extras = "dev help browser playwright".split()
|
||||
|
||||
setup(
|
||||
name="aider-chat",
|
||||
version=__version__,
|
||||
packages=packages,
|
||||
include_package_data=True,
|
||||
package_data={
|
||||
"aider": ["queries/*.scm"],
|
||||
"aider.website": ["**/*.md"],
|
||||
},
|
||||
exclude_package_data={"aider.website": exclude_website_pats},
|
||||
install_requires=requirements,
|
||||
extras_require={extra: get_requirements(extra) for extra in extras},
|
||||
python_requires=">=3.9,<3.13",
|
||||
entry_points={
|
||||
"console_scripts": [
|
||||
"aider = aider.main:main",
|
||||
],
|
||||
},
|
||||
description="Aider is AI pair programming in your terminal",
|
||||
long_description=long_description,
|
||||
long_description_content_type="text/markdown",
|
||||
url="https://github.com/paul-gauthier/aider",
|
||||
classifiers=[
|
||||
"Development Status :: 4 - Beta",
|
||||
"Environment :: Console",
|
||||
"Intended Audience :: Developers",
|
||||
"License :: OSI Approved :: Apache Software License",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
"Programming Language :: Python",
|
||||
"Topic :: Software Development",
|
||||
],
|
||||
)
|
|
@ -189,6 +189,33 @@ class TestCoder(unittest.TestCase):
|
|||
|
||||
self.assertEqual(coder.abs_fnames, set([str(fname.resolve())]))
|
||||
|
||||
def test_check_for_file_mentions_read_only(self):
|
||||
with GitTemporaryDirectory():
|
||||
io = InputOutput(
|
||||
pretty=False,
|
||||
yes=True,
|
||||
)
|
||||
coder = Coder.create(self.GPT35, None, io)
|
||||
|
||||
fname = Path("readonly_file.txt")
|
||||
fname.touch()
|
||||
|
||||
coder.abs_read_only_fnames.add(str(fname.resolve()))
|
||||
|
||||
# Mock the get_tracked_files method
|
||||
mock = MagicMock()
|
||||
mock.return_value = set([str(fname)])
|
||||
coder.repo.get_tracked_files = mock
|
||||
|
||||
# Call the check_for_file_mentions method
|
||||
result = coder.check_for_file_mentions(f"Please check {fname}!")
|
||||
|
||||
# Assert that the method returns None (user not asked to add the file)
|
||||
self.assertIsNone(result)
|
||||
|
||||
# Assert that abs_fnames is still empty (file not added)
|
||||
self.assertEqual(coder.abs_fnames, set())
|
||||
|
||||
def test_check_for_subdir_mention(self):
|
||||
with GitTemporaryDirectory():
|
||||
io = InputOutput(pretty=False, yes=True)
|
||||
|
@ -259,7 +286,7 @@ class TestCoder(unittest.TestCase):
|
|||
files = [file1, file2]
|
||||
|
||||
# Initialize the Coder object with the mocked IO and mocked repo
|
||||
coder = Coder.create(self.GPT35, None, io=InputOutput(), fnames=files, pretty=False)
|
||||
coder = Coder.create(self.GPT35, None, io=InputOutput(), fnames=files)
|
||||
|
||||
def mock_send(*args, **kwargs):
|
||||
coder.partial_response_content = "ok"
|
||||
|
@ -286,7 +313,7 @@ class TestCoder(unittest.TestCase):
|
|||
files = [file1, file2]
|
||||
|
||||
# Initialize the Coder object with the mocked IO and mocked repo
|
||||
coder = Coder.create(self.GPT35, None, io=InputOutput(), fnames=files, pretty=False)
|
||||
coder = Coder.create(self.GPT35, None, io=InputOutput(), fnames=files)
|
||||
|
||||
def mock_send(*args, **kwargs):
|
||||
coder.partial_response_content = "ok"
|
||||
|
@ -377,7 +404,7 @@ class TestCoder(unittest.TestCase):
|
|||
fname = Path("file.txt")
|
||||
|
||||
io = InputOutput(yes=True)
|
||||
coder = Coder.create(self.GPT35, "diff", io=io, fnames=[str(fname)], pretty=False)
|
||||
coder = Coder.create(self.GPT35, "diff", io=io, fnames=[str(fname)])
|
||||
|
||||
self.assertTrue(fname.exists())
|
||||
|
||||
|
@ -434,9 +461,7 @@ new
|
|||
fname1.write_text("ONE\n")
|
||||
|
||||
io = InputOutput(yes=True)
|
||||
coder = Coder.create(
|
||||
self.GPT35, "diff", io=io, fnames=[str(fname1), str(fname2)], pretty=False
|
||||
)
|
||||
coder = Coder.create(self.GPT35, "diff", io=io, fnames=[str(fname1), str(fname2)])
|
||||
|
||||
def mock_send(*args, **kwargs):
|
||||
coder.partial_response_content = f"""
|
||||
|
@ -489,7 +514,7 @@ TWO
|
|||
fname2.write_text("OTHER\n")
|
||||
|
||||
io = InputOutput(yes=True)
|
||||
coder = Coder.create(self.GPT35, "diff", io=io, fnames=[str(fname)], pretty=False)
|
||||
coder = Coder.create(self.GPT35, "diff", io=io, fnames=[str(fname)])
|
||||
|
||||
def mock_send(*args, **kwargs):
|
||||
coder.partial_response_content = f"""
|
||||
|
@ -567,7 +592,7 @@ three
|
|||
repo.git.commit("-m", "initial")
|
||||
|
||||
io = InputOutput(yes=True)
|
||||
coder = Coder.create(self.GPT35, "diff", io=io, fnames=[str(fname)], pretty=False)
|
||||
coder = Coder.create(self.GPT35, "diff", io=io, fnames=[str(fname)])
|
||||
|
||||
def mock_send(*args, **kwargs):
|
||||
coder.partial_response_content = f"""
|
||||
|
@ -640,7 +665,7 @@ two
|
|||
|
||||
def test_check_for_urls(self):
|
||||
io = InputOutput(yes=True)
|
||||
coder = Coder.create(self.GPT35, None, io=io, pretty=False)
|
||||
coder = Coder.create(self.GPT35, None, io=io)
|
||||
coder.commands.scraper = MagicMock()
|
||||
coder.commands.scraper.scrape = MagicMock(return_value="some content")
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ from unittest import TestCase, mock
|
|||
import git
|
||||
|
||||
from aider.coders import Coder
|
||||
from aider.commands import Commands
|
||||
from aider.commands import Commands, SwitchCoder
|
||||
from aider.dump import dump # noqa: F401
|
||||
from aider.io import InputOutput
|
||||
from aider.models import Model
|
||||
|
@ -537,6 +537,62 @@ class TestCommands(TestCase):
|
|||
commands.cmd_add("file.txt")
|
||||
self.assertEqual(coder.abs_fnames, set())
|
||||
|
||||
def test_cmd_add_read_only_file(self):
|
||||
with GitTemporaryDirectory():
|
||||
# Initialize the Commands and InputOutput objects
|
||||
io = InputOutput(pretty=False, yes=True)
|
||||
from aider.coders import Coder
|
||||
|
||||
coder = Coder.create(self.GPT35, None, io)
|
||||
commands = Commands(io, coder)
|
||||
|
||||
# Create a test file
|
||||
test_file = Path("test_read_only.txt")
|
||||
test_file.write_text("Test content")
|
||||
|
||||
# Add the file as read-only
|
||||
commands.cmd_read(str(test_file))
|
||||
|
||||
# Verify it's in abs_read_only_fnames
|
||||
self.assertTrue(
|
||||
any(
|
||||
os.path.samefile(str(test_file.resolve()), fname)
|
||||
for fname in coder.abs_read_only_fnames
|
||||
)
|
||||
)
|
||||
|
||||
# Try to add the read-only file
|
||||
commands.cmd_add(str(test_file))
|
||||
|
||||
# It's not in the repo, should not do anything
|
||||
self.assertFalse(
|
||||
any(os.path.samefile(str(test_file.resolve()), fname) for fname in coder.abs_fnames)
|
||||
)
|
||||
self.assertTrue(
|
||||
any(
|
||||
os.path.samefile(str(test_file.resolve()), fname)
|
||||
for fname in coder.abs_read_only_fnames
|
||||
)
|
||||
)
|
||||
|
||||
repo = git.Repo()
|
||||
repo.git.add(str(test_file))
|
||||
repo.git.commit("-m", "initial")
|
||||
|
||||
# Try to add the read-only file
|
||||
commands.cmd_add(str(test_file))
|
||||
|
||||
# Verify it's now in abs_fnames and not in abs_read_only_fnames
|
||||
self.assertTrue(
|
||||
any(os.path.samefile(str(test_file.resolve()), fname) for fname in coder.abs_fnames)
|
||||
)
|
||||
self.assertFalse(
|
||||
any(
|
||||
os.path.samefile(str(test_file.resolve()), fname)
|
||||
for fname in coder.abs_read_only_fnames
|
||||
)
|
||||
)
|
||||
|
||||
def test_cmd_test_unbound_local_error(self):
|
||||
with ChdirTemporaryDirectory():
|
||||
io = InputOutput(pretty=False, yes=False)
|
||||
|
@ -731,6 +787,140 @@ class TestCommands(TestCase):
|
|||
self.assertNotIn(fname2, str(coder.abs_fnames))
|
||||
self.assertNotIn(fname3, str(coder.abs_fnames))
|
||||
|
||||
def test_cmd_read(self):
|
||||
with GitTemporaryDirectory():
|
||||
io = InputOutput(pretty=False, yes=False)
|
||||
coder = Coder.create(self.GPT35, None, io)
|
||||
commands = Commands(io, coder)
|
||||
|
||||
# Create a test file
|
||||
test_file = Path("test_read.txt")
|
||||
test_file.write_text("Test content")
|
||||
|
||||
# Test the /read command
|
||||
commands.cmd_read(str(test_file))
|
||||
|
||||
# Check if the file was added to abs_read_only_fnames
|
||||
self.assertTrue(
|
||||
any(
|
||||
os.path.samefile(str(test_file.resolve()), fname)
|
||||
for fname in coder.abs_read_only_fnames
|
||||
)
|
||||
)
|
||||
|
||||
# Test dropping the read-only file
|
||||
commands.cmd_drop(str(test_file))
|
||||
|
||||
# Check if the file was removed from abs_read_only_fnames
|
||||
self.assertFalse(
|
||||
any(
|
||||
os.path.samefile(str(test_file.resolve()), fname)
|
||||
for fname in coder.abs_read_only_fnames
|
||||
)
|
||||
)
|
||||
|
||||
def test_cmd_read_with_external_file(self):
|
||||
with tempfile.NamedTemporaryFile(mode="w", delete=False) as external_file:
|
||||
external_file.write("External file content")
|
||||
external_file_path = external_file.name
|
||||
|
||||
try:
|
||||
with GitTemporaryDirectory():
|
||||
io = InputOutput(pretty=False, yes=False)
|
||||
coder = Coder.create(self.GPT35, None, io)
|
||||
commands = Commands(io, coder)
|
||||
|
||||
# Test the /read command with an external file
|
||||
commands.cmd_read(external_file_path)
|
||||
|
||||
# Check if the external file was added to abs_read_only_fnames
|
||||
real_external_file_path = os.path.realpath(external_file_path)
|
||||
self.assertTrue(
|
||||
any(
|
||||
os.path.samefile(real_external_file_path, fname)
|
||||
for fname in coder.abs_read_only_fnames
|
||||
)
|
||||
)
|
||||
|
||||
# Test dropping the external read-only file
|
||||
commands.cmd_drop(Path(external_file_path).name)
|
||||
|
||||
# Check if the file was removed from abs_read_only_fnames
|
||||
self.assertFalse(
|
||||
any(
|
||||
os.path.samefile(real_external_file_path, fname)
|
||||
for fname in coder.abs_read_only_fnames
|
||||
)
|
||||
)
|
||||
finally:
|
||||
os.unlink(external_file_path)
|
||||
|
||||
def test_cmd_diff(self):
|
||||
with GitTemporaryDirectory() as repo_dir:
|
||||
repo = git.Repo(repo_dir)
|
||||
io = InputOutput(pretty=False, yes=True)
|
||||
coder = Coder.create(self.GPT35, None, io)
|
||||
commands = Commands(io, coder)
|
||||
|
||||
# Create and commit a file
|
||||
filename = "test_file.txt"
|
||||
file_path = Path(repo_dir) / filename
|
||||
file_path.write_text("Initial content\n")
|
||||
repo.git.add(filename)
|
||||
repo.git.commit("-m", "Initial commit\n")
|
||||
|
||||
# Modify the file to make it dirty
|
||||
file_path.write_text("Modified content")
|
||||
|
||||
# Mock repo.get_commit_message to return a canned commit message
|
||||
with mock.patch.object(
|
||||
coder.repo, "get_commit_message", return_value="Canned commit message"
|
||||
):
|
||||
# Run cmd_commit
|
||||
commands.cmd_commit()
|
||||
|
||||
# Capture the output of cmd_diff
|
||||
with mock.patch("builtins.print") as mock_print:
|
||||
commands.cmd_diff("")
|
||||
|
||||
# Check if the diff output is correct
|
||||
mock_print.assert_called_with(mock.ANY)
|
||||
diff_output = mock_print.call_args[0][0]
|
||||
self.assertIn("-Initial content", diff_output)
|
||||
self.assertIn("+Modified content", diff_output)
|
||||
|
||||
# Modify the file again
|
||||
file_path.write_text("Further modified content")
|
||||
|
||||
# Run cmd_commit again
|
||||
commands.cmd_commit()
|
||||
|
||||
# Capture the output of cmd_diff
|
||||
with mock.patch("builtins.print") as mock_print:
|
||||
commands.cmd_diff("")
|
||||
|
||||
# Check if the diff output is correct
|
||||
mock_print.assert_called_with(mock.ANY)
|
||||
diff_output = mock_print.call_args[0][0]
|
||||
self.assertIn("-Modified content", diff_output)
|
||||
self.assertIn("+Further modified content", diff_output)
|
||||
|
||||
# Modify the file a third time
|
||||
file_path.write_text("Final modified content")
|
||||
|
||||
# Run cmd_commit again
|
||||
commands.cmd_commit()
|
||||
|
||||
# Capture the output of cmd_diff
|
||||
with mock.patch("builtins.print") as mock_print:
|
||||
commands.cmd_diff("")
|
||||
|
||||
# Check if the diff output is correct
|
||||
mock_print.assert_called_with(mock.ANY)
|
||||
diff_output = mock_print.call_args[0][0]
|
||||
self.assertIn("-Further modified content", diff_output)
|
||||
self.assertIn("+Final modified content", diff_output)
|
||||
|
||||
def test_cmd_ask(self):
|
||||
io = InputOutput(pretty=False, yes=True)
|
||||
coder = Coder.create(self.GPT35, None, io)
|
||||
|
@ -742,17 +932,12 @@ class TestCommands(TestCase):
|
|||
with mock.patch("aider.coders.Coder.run") as mock_run:
|
||||
mock_run.return_value = canned_reply
|
||||
|
||||
with self.assertRaises(SwitchCoder):
|
||||
commands.cmd_ask(question)
|
||||
|
||||
mock_run.assert_called_once()
|
||||
mock_run.assert_called_once_with(question)
|
||||
|
||||
self.assertEqual(len(coder.cur_messages), 2)
|
||||
self.assertEqual(coder.cur_messages[0]["role"], "user")
|
||||
self.assertEqual(coder.cur_messages[0]["content"], question)
|
||||
self.assertEqual(coder.cur_messages[1]["role"], "assistant")
|
||||
self.assertEqual(coder.cur_messages[1]["content"], canned_reply)
|
||||
|
||||
def test_cmd_lint_with_dirty_file(self):
|
||||
with GitTemporaryDirectory() as repo_dir:
|
||||
repo = git.Repo(repo_dir)
|
||||
|
|
|
@ -297,7 +297,7 @@ These changes replace the `subprocess.run` patches with `subprocess.check_output
|
|||
files = [file1]
|
||||
|
||||
# Initialize the Coder object with the mocked IO and mocked repo
|
||||
coder = Coder.create(self.GPT35, "diff", io=InputOutput(), fnames=files, pretty=False)
|
||||
coder = Coder.create(self.GPT35, "diff", io=InputOutput(), fnames=files)
|
||||
|
||||
def mock_send(*args, **kwargs):
|
||||
coder.partial_response_content = f"""
|
||||
|
@ -340,7 +340,6 @@ new
|
|||
io=InputOutput(dry_run=True),
|
||||
fnames=files,
|
||||
dry_run=True,
|
||||
pretty=False,
|
||||
)
|
||||
|
||||
def mock_send(*args, **kwargs):
|
||||
|
|
|
@ -149,17 +149,6 @@ class TestMain(TestCase):
|
|||
_, kwargs = MockCoder.call_args
|
||||
assert kwargs["dirty_commits"] is True
|
||||
assert kwargs["auto_commits"] is True
|
||||
assert kwargs["pretty"] is True
|
||||
|
||||
with patch("aider.coders.Coder.create") as MockCoder:
|
||||
main(["--no-pretty"], input=DummyInput())
|
||||
_, kwargs = MockCoder.call_args
|
||||
assert kwargs["pretty"] is False
|
||||
|
||||
with patch("aider.coders.Coder.create") as MockCoder:
|
||||
main(["--pretty"], input=DummyInput())
|
||||
_, kwargs = MockCoder.call_args
|
||||
assert kwargs["pretty"] is True
|
||||
|
||||
with patch("aider.coders.Coder.create") as MockCoder:
|
||||
main(["--no-dirty-commits"], input=DummyInput())
|
||||
|
@ -235,6 +224,15 @@ class TestMain(TestCase):
|
|||
|
||||
main(["--yes", fname, "--encoding", "iso-8859-15"])
|
||||
|
||||
def test_main_exit_calls_version_check(self):
|
||||
with GitTemporaryDirectory():
|
||||
with patch("aider.main.check_version") as mock_check_version, patch(
|
||||
"aider.main.InputOutput"
|
||||
) as mock_input_output:
|
||||
main(["--exit"], input=DummyInput(), output=DummyOutput())
|
||||
mock_check_version.assert_called_once()
|
||||
mock_input_output.assert_called_once()
|
||||
|
||||
@patch("aider.main.InputOutput")
|
||||
@patch("aider.coders.base_coder.Coder.run")
|
||||
def test_main_message_adds_to_input_history(self, mock_run, MockInputOutput):
|
||||
|
@ -396,3 +394,36 @@ class TestMain(TestCase):
|
|||
output=DummyOutput(),
|
||||
)
|
||||
MockRepoMap.assert_called_once()
|
||||
|
||||
def test_read_option(self):
|
||||
with GitTemporaryDirectory():
|
||||
test_file = "test_file.txt"
|
||||
Path(test_file).touch()
|
||||
|
||||
coder = main(
|
||||
["--read", test_file, "--exit", "--yes"],
|
||||
input=DummyInput(),
|
||||
output=DummyOutput(),
|
||||
return_coder=True,
|
||||
)
|
||||
|
||||
self.assertIn(str(Path(test_file).resolve()), coder.abs_read_only_fnames)
|
||||
|
||||
def test_read_option_with_external_file(self):
|
||||
with tempfile.NamedTemporaryFile(mode="w", delete=False) as external_file:
|
||||
external_file.write("External file content")
|
||||
external_file_path = external_file.name
|
||||
|
||||
try:
|
||||
with GitTemporaryDirectory():
|
||||
coder = main(
|
||||
["--read", external_file_path, "--exit", "--yes"],
|
||||
input=DummyInput(),
|
||||
output=DummyOutput(),
|
||||
return_coder=True,
|
||||
)
|
||||
|
||||
real_external_file_path = os.path.realpath(external_file_path)
|
||||
self.assertIn(real_external_file_path, coder.abs_read_only_fnames)
|
||||
finally:
|
||||
os.unlink(external_file_path)
|
||||
|
|
|
@ -4,7 +4,7 @@ from unittest.mock import MagicMock, patch
|
|||
import httpx
|
||||
|
||||
from aider.llm import litellm
|
||||
from aider.sendchat import send_with_retries
|
||||
from aider.sendchat import simple_send_with_retries
|
||||
|
||||
|
||||
class PrintCalled(Exception):
|
||||
|
@ -14,7 +14,7 @@ class PrintCalled(Exception):
|
|||
class TestSendChat(unittest.TestCase):
|
||||
@patch("litellm.completion")
|
||||
@patch("builtins.print")
|
||||
def test_send_with_retries_rate_limit_error(self, mock_print, mock_completion):
|
||||
def test_simple_send_with_retries_rate_limit_error(self, mock_print, mock_completion):
|
||||
mock = MagicMock()
|
||||
mock.status_code = 500
|
||||
|
||||
|
@ -29,19 +29,19 @@ class TestSendChat(unittest.TestCase):
|
|||
None,
|
||||
]
|
||||
|
||||
# Call the send_with_retries method
|
||||
send_with_retries("model", ["message"], None, False)
|
||||
# Call the simple_send_with_retries method
|
||||
simple_send_with_retries("model", ["message"])
|
||||
mock_print.assert_called_once()
|
||||
|
||||
@patch("litellm.completion")
|
||||
@patch("builtins.print")
|
||||
def test_send_with_retries_connection_error(self, mock_print, mock_completion):
|
||||
def test_simple_send_with_retries_connection_error(self, mock_print, mock_completion):
|
||||
# Set up the mock to raise
|
||||
mock_completion.side_effect = [
|
||||
httpx.ConnectError("Connection error"),
|
||||
None,
|
||||
]
|
||||
|
||||
# Call the send_with_retries method
|
||||
send_with_retries("model", ["message"], None, False)
|
||||
# Call the simple_send_with_retries method
|
||||
simple_send_with_retries("model", ["message"])
|
||||
mock_print.assert_called_once()
|
||||
|
|
|
@ -288,9 +288,7 @@ after b
|
|||
files = [file1]
|
||||
|
||||
# Initialize the Coder object with the mocked IO and mocked repo
|
||||
coder = Coder.create(
|
||||
self.GPT35, "whole", io=InputOutput(), fnames=files, stream=False, pretty=False
|
||||
)
|
||||
coder = Coder.create(self.GPT35, "whole", io=InputOutput(), fnames=files, stream=False)
|
||||
|
||||
# no trailing newline so the response content below doesn't add ANOTHER newline
|
||||
new_content = "new\ntwo\nthree"
|
||||
|
|
|
@ -22,7 +22,13 @@ class TestHelp(unittest.TestCase):
|
|||
help_coder_run = MagicMock(return_value="")
|
||||
aider.coders.HelpCoder.run = help_coder_run
|
||||
|
||||
try:
|
||||
commands.cmd_help("hi")
|
||||
except aider.commands.SwitchCoder:
|
||||
pass
|
||||
else:
|
||||
# If no exception was raised, fail the test
|
||||
assert False, "SwitchCoder exception was not raised"
|
||||
|
||||
help_coder_run.assert_called_once()
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ class TestScrape(unittest.TestCase):
|
|||
self.commands.io.tool_error = mock_print_error
|
||||
|
||||
# Run the cmd_web command
|
||||
result = self.commands.cmd_web("https://example.com")
|
||||
result = self.commands.cmd_web("https://example.com", paginate=False)
|
||||
|
||||
# Assert that the result contains some content
|
||||
self.assertIsNotNone(result)
|
||||
|
@ -100,7 +100,7 @@ class TestScrape(unittest.TestCase):
|
|||
|
||||
# Mock the necessary objects and methods
|
||||
scraper.scrape_with_playwright = MagicMock()
|
||||
scraper.scrape_with_playwright.return_value = None
|
||||
scraper.scrape_with_playwright.return_value = (None, None)
|
||||
|
||||
# Call the scrape method
|
||||
result = scraper.scrape("https://example.com")
|
||||
|
@ -113,6 +113,54 @@ class TestScrape(unittest.TestCase):
|
|||
"Failed to retrieve content from https://example.com"
|
||||
)
|
||||
|
||||
# Reset the mock
|
||||
mock_print_error.reset_mock()
|
||||
|
||||
# Test with a different return value
|
||||
scraper.scrape_with_playwright.return_value = ("Some content", "text/html")
|
||||
result = scraper.scrape("https://example.com")
|
||||
|
||||
# Assert that the result is not None
|
||||
self.assertIsNotNone(result)
|
||||
|
||||
# Assert that print_error was not called
|
||||
mock_print_error.assert_not_called()
|
||||
|
||||
def test_scrape_text_plain(self):
|
||||
# Create a Scraper instance
|
||||
scraper = Scraper(print_error=MagicMock(), playwright_available=True)
|
||||
|
||||
# Mock the scrape_with_playwright method
|
||||
plain_text = "This is plain text content."
|
||||
scraper.scrape_with_playwright = MagicMock(return_value=(plain_text, "text/plain"))
|
||||
|
||||
# Call the scrape method
|
||||
result = scraper.scrape("https://example.com")
|
||||
|
||||
# Assert that the result is the same as the input plain text
|
||||
self.assertEqual(result, plain_text)
|
||||
|
||||
def test_scrape_text_html(self):
|
||||
# Create a Scraper instance
|
||||
scraper = Scraper(print_error=MagicMock(), playwright_available=True)
|
||||
|
||||
# Mock the scrape_with_playwright method
|
||||
html_content = "<html><body><h1>Test</h1><p>This is HTML content.</p></body></html>"
|
||||
scraper.scrape_with_playwright = MagicMock(return_value=(html_content, "text/html"))
|
||||
|
||||
# Mock the html_to_markdown method
|
||||
expected_markdown = "# Test\n\nThis is HTML content."
|
||||
scraper.html_to_markdown = MagicMock(return_value=expected_markdown)
|
||||
|
||||
# Call the scrape method
|
||||
result = scraper.scrape("https://example.com")
|
||||
|
||||
# Assert that the result is the expected markdown
|
||||
self.assertEqual(result, expected_markdown)
|
||||
|
||||
# Assert that html_to_markdown was called with the HTML content
|
||||
scraper.html_to_markdown.assert_called_once_with(html_content)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue