diff --git a/aider/__init__.py b/aider/__init__.py index b2dc4171a2e..c41ed70f912 100644 --- a/aider/__init__.py +++ b/aider/__init__.py @@ -1,6 +1,6 @@ from packaging import version -__version__ = "0.88.0.dev" +__version__ = "0.88.1.dev" safe_version = __version__ try: diff --git a/aider/coders/base_coder.py b/aider/coders/base_coder.py index 3645fedf700..848cb3b2bf5 100755 --- a/aider/coders/base_coder.py +++ b/aider/coders/base_coder.py @@ -1248,7 +1248,7 @@ async def run_one(self, user_message, preproc): else: message = self.reflected_message - def check_and_open_urls(self, exc, friendly_msg=None): + async def check_and_open_urls(self, exc, friendly_msg=None): """Check exception for URLs, offer to open in a browser, with user-friendly error msgs.""" text = str(exc) @@ -1264,7 +1264,7 @@ def check_and_open_urls(self, exc, friendly_msg=None): urls = list(set(url_pattern.findall(text))) for url in urls: url = url.rstrip(".',\"}") # Added } to the characters to strip - self.io.offer_url(url) + await self.io.offer_url(url) return urls async def check_for_urls(self, inp: str) -> List[str]: @@ -1841,7 +1841,7 @@ async def send_message(self, inp): if not should_retry: self.mdstream = None - self.check_and_open_urls(err, ex_info.description) + await self.check_and_open_urls(err, ex_info.description) break err_msg = str(err) @@ -1906,7 +1906,7 @@ async def send_message(self, inp): ), ] - self.show_exhausted_error() + await self.show_exhausted_error() self.num_exhausted_context_windows += 1 return @@ -2402,7 +2402,7 @@ def get_tool_list(self): async def reply_completed(self): pass - def show_exhausted_error(self): + async def show_exhausted_error(self): output_tokens = 0 if self.partial_response_content: output_tokens = self.main_model.token_count(self.partial_response_content) @@ -2453,7 +2453,7 @@ def show_exhausted_error(self): res = "".join([line + "\n" for line in res]) self.io.tool_error(res) - self.io.offer_url(urls.token_limits) + await self.io.offer_url(urls.token_limits) def lint_edited(self, fnames): res = "" diff --git a/aider/exceptions.py b/aider/exceptions.py index 172750c9b30..2aaa7d93206 100644 --- a/aider/exceptions.py +++ b/aider/exceptions.py @@ -28,6 +28,7 @@ class ExInfo: "The API provider has refused the request due to a safety policy about the content.", ), ExInfo("ContextWindowExceededError", False, None), # special case handled in base_coder + ExInfo("ErrorEventError", True, None), ExInfo("ImageFetchError", True, "The API cannot fetch an image"), ExInfo("InternalServerError", True, "The API provider's servers are down or overloaded."), ExInfo("InvalidRequestError", True, None), diff --git a/aider/main.py b/aider/main.py index 1c7781e0d16..a8b795cf510 100644 --- a/aider/main.py +++ b/aider/main.py @@ -412,7 +412,7 @@ def register_litellm_models(git_root, model_metadata_fname, io, verbose=False): return 1 -def sanity_check_repo(repo, io): +async def sanity_check_repo(repo, io): if not repo: return True @@ -443,7 +443,7 @@ def sanity_check_repo(repo, io): io.tool_error("Aider only works with git repos with version number 1 or 2.") io.tool_output("You may be able to convert your repo: git update-index --index-version=2") io.tool_output("Or run aider --no-git to proceed without using git.") - io.offer_url(urls.git_index_version, "Open documentation url for more info?") + await io.offer_url(urls.git_index_version, "Open documentation url for more info?") return False io.tool_error("Unable to read git repository, it may be corrupt?") @@ -783,7 +783,7 @@ def get_io(pretty): io.tool_output(cmd_line, log_only=True) is_first_run = is_first_run_of_new_version(io, verbose=args.verbose) - check_and_load_imports(io, is_first_run, verbose=args.verbose) + await check_and_load_imports(io, is_first_run, verbose=args.verbose) register_models(git_root, args.model_settings_file, io, verbose=args.verbose) register_litellm_models(git_root, args.model_metadata_file, io, verbose=args.verbose) @@ -844,7 +844,7 @@ def get_io(pretty): io.tool_error( f"Unable to proceed without an OpenRouter API key for model '{args.model}'." ) - io.offer_url(urls.models_and_keys, "Open documentation URL for more info?") + await io.offer_url(urls.models_and_keys, "Open documentation URL for more info?") analytics.event( "exit", reason="OpenRouter key missing for specified model and OAuth failed/declined", @@ -926,7 +926,7 @@ def get_io(pretty): io.tool_output("You can skip this check with --no-show-model-warnings") try: - io.offer_url(urls.model_warnings, "Open documentation url for more info?") + await io.offer_url(urls.model_warnings, "Open documentation url for more info?") io.tool_output() except KeyboardInterrupt: analytics.event("exit", reason="Keyboard interrupt during model warnings") @@ -954,7 +954,7 @@ def get_io(pretty): pass if not args.skip_sanity_check_repo: - if not sanity_check_repo(repo, io): + if not await sanity_check_repo(repo, io): analytics.event("exit", reason="Repository sanity check failed") return 1 @@ -1059,7 +1059,7 @@ def get_io(pretty): ) except UnknownEditFormat as err: io.tool_error(str(err)) - io.offer_url(urls.edit_formats, "Open documentation about edit formats?") + await io.offer_url(urls.edit_formats, "Open documentation about edit formats?") analytics.event("exit", reason="Unknown edit format") return 1 except ValueError as err: @@ -1152,7 +1152,7 @@ def get_io(pretty): webbrowser.open(urls.release_notes) elif args.show_release_notes is None and is_first_run: io.tool_output() - io.offer_url( + await io.offer_url( urls.release_notes, "Would you like to see what's new in this version?", allow_never=False, @@ -1276,7 +1276,7 @@ def is_first_run_of_new_version(io, verbose=False): return True # Safer to assume it's a first run if we hit an error -def check_and_load_imports(io, is_first_run, verbose=False): +async def check_and_load_imports(io, is_first_run, verbose=False): try: if is_first_run: if verbose: @@ -1288,7 +1288,7 @@ def check_and_load_imports(io, is_first_run, verbose=False): except Exception as err: io.tool_error(str(err)) io.tool_output("Error loading required imports. Did you install aider properly?") - io.offer_url(urls.install_properly, "Open documentation url for more info?") + await io.offer_url(urls.install_properly, "Open documentation url for more info?") sys.exit(1) if verbose: diff --git a/aider/onboarding.py b/aider/onboarding.py index 0cd6019fa87..f552e33e82b 100644 --- a/aider/onboarding.py +++ b/aider/onboarding.py @@ -146,7 +146,7 @@ async def select_default_model(args, io, analytics): if model: return model - io.offer_url(urls.models_and_keys, "Open documentation URL for more info?") + await io.offer_url(urls.models_and_keys, "Open documentation URL for more info?") # Helper function to find an available port diff --git a/tests/basic/test_sanity_check_repo.py b/tests/basic/test_sanity_check_repo.py index 860572ec54f..5c3afeb4a3e 100644 --- a/tests/basic/test_sanity_check_repo.py +++ b/tests/basic/test_sanity_check_repo.py @@ -1,3 +1,4 @@ +import asyncio import os import shutil import struct @@ -81,7 +82,7 @@ def get_tracked_files_side_effect(): return mock_repo -def test_detached_head_state(create_repo, mock_io): +async def test_detached_head_state(create_repo, mock_io): repo_path, repo = create_repo # Detach the HEAD detach_head(repo) @@ -90,7 +91,7 @@ def test_detached_head_state(create_repo, mock_io): mock_repo_obj = mock_repo_wrapper(repo) # Call the function - result = sanity_check_repo(mock_repo_obj, mock_io) + result = await sanity_check_repo(mock_repo_obj, mock_io) # Assert that the function returns True assert result is True @@ -101,7 +102,7 @@ def test_detached_head_state(create_repo, mock_io): @mock.patch("webbrowser.open") -def test_git_index_version_greater_than_2(mock_browser, create_repo, mock_io): +async def test_git_index_version_greater_than_2(mock_browser, create_repo, mock_io): repo_path, repo = create_repo # Set the git index version to 3 set_git_index_version(str(repo_path), 3) @@ -110,8 +111,12 @@ def test_git_index_version_greater_than_2(mock_browser, create_repo, mock_io): git_error = GitError("index version in (1, 2) is required") mock_repo_obj = mock_repo_wrapper(repo, git_repo_error=git_error) + # Configure the mock to return an async mock for offer_url + mock_io.offer_url.return_value = asyncio.Future() + mock_io.offer_url.return_value.set_result(False) + # Call the function - result = sanity_check_repo(mock_repo_obj, mock_io) + result = await sanity_check_repo(mock_repo_obj, mock_io) # Assert that the function returns False assert result is False @@ -133,7 +138,7 @@ def test_git_index_version_greater_than_2(mock_browser, create_repo, mock_io): ) -def test_bare_repository(create_repo, mock_io, tmp_path): +async def test_bare_repository(create_repo, mock_io, tmp_path): # Initialize a bare repository bare_repo_path = tmp_path / "bare_repo.git" bare_repo = Repo.init(bare_repo_path, bare=True) @@ -142,7 +147,7 @@ def test_bare_repository(create_repo, mock_io, tmp_path): mock_repo_obj = mock_repo_wrapper(bare_repo) # Call the function - result = sanity_check_repo(mock_repo_obj, mock_io) + result = await sanity_check_repo(mock_repo_obj, mock_io) # Assert that the function returns False assert result is False @@ -152,7 +157,7 @@ def test_bare_repository(create_repo, mock_io, tmp_path): mock_io.tool_output.assert_not_called() -def test_sanity_check_repo_with_corrupt_repo(create_repo, mock_io): +async def test_sanity_check_repo_with_corrupt_repo(create_repo, mock_io): repo_path, repo = create_repo # Simulate a corrupt repository by removing the .git directory shutil.rmtree(os.path.join(repo_path, ".git")) @@ -162,7 +167,7 @@ def test_sanity_check_repo_with_corrupt_repo(create_repo, mock_io): mock_repo_obj = mock_repo_wrapper(repo, git_repo_error=git_error) # Call the function - result = sanity_check_repo(mock_repo_obj, mock_io) + result = await sanity_check_repo(mock_repo_obj, mock_io) # Assert that the function returns False assert result is False @@ -172,9 +177,9 @@ def test_sanity_check_repo_with_corrupt_repo(create_repo, mock_io): mock_io.tool_output.assert_called_with(str(git_error)) -def test_sanity_check_repo_with_no_repo(mock_io): +async def test_sanity_check_repo_with_no_repo(mock_io): # Call the function with repo=None - result = sanity_check_repo(None, mock_io) + result = await sanity_check_repo(None, mock_io) # Assert that the function returns True assert result is True