aider-ce 0.87.13.dev3__py3-none-any.whl → 0.88.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of aider-ce might be problematic. Click here for more details.

Files changed (60) hide show
  1. aider/__init__.py +1 -1
  2. aider/_version.py +2 -2
  3. aider/args.py +6 -0
  4. aider/coders/architect_coder.py +3 -3
  5. aider/coders/base_coder.py +505 -184
  6. aider/coders/context_coder.py +1 -1
  7. aider/coders/editblock_func_coder.py +2 -2
  8. aider/coders/navigator_coder.py +451 -649
  9. aider/coders/navigator_legacy_prompts.py +49 -284
  10. aider/coders/navigator_prompts.py +46 -473
  11. aider/coders/search_replace.py +0 -0
  12. aider/coders/wholefile_func_coder.py +2 -2
  13. aider/commands.py +56 -44
  14. aider/history.py +14 -12
  15. aider/io.py +354 -117
  16. aider/llm.py +12 -4
  17. aider/main.py +22 -19
  18. aider/mcp/__init__.py +65 -2
  19. aider/mcp/server.py +37 -11
  20. aider/models.py +45 -20
  21. aider/onboarding.py +4 -4
  22. aider/repo.py +7 -7
  23. aider/resources/model-metadata.json +8 -8
  24. aider/scrape.py +2 -2
  25. aider/sendchat.py +185 -15
  26. aider/tools/__init__.py +44 -23
  27. aider/tools/command.py +18 -0
  28. aider/tools/command_interactive.py +18 -0
  29. aider/tools/delete_block.py +23 -0
  30. aider/tools/delete_line.py +19 -1
  31. aider/tools/delete_lines.py +20 -1
  32. aider/tools/extract_lines.py +25 -2
  33. aider/tools/git.py +142 -0
  34. aider/tools/grep.py +47 -2
  35. aider/tools/indent_lines.py +25 -0
  36. aider/tools/insert_block.py +26 -0
  37. aider/tools/list_changes.py +15 -0
  38. aider/tools/ls.py +24 -1
  39. aider/tools/make_editable.py +18 -0
  40. aider/tools/make_readonly.py +19 -0
  41. aider/tools/remove.py +22 -0
  42. aider/tools/replace_all.py +21 -0
  43. aider/tools/replace_line.py +20 -1
  44. aider/tools/replace_lines.py +21 -1
  45. aider/tools/replace_text.py +22 -0
  46. aider/tools/show_numbered_context.py +18 -0
  47. aider/tools/undo_change.py +15 -0
  48. aider/tools/update_todo_list.py +131 -0
  49. aider/tools/view.py +23 -0
  50. aider/tools/view_files_at_glob.py +32 -27
  51. aider/tools/view_files_matching.py +51 -37
  52. aider/tools/view_files_with_symbol.py +41 -54
  53. aider/tools/view_todo_list.py +57 -0
  54. aider/waiting.py +20 -203
  55. {aider_ce-0.87.13.dev3.dist-info → aider_ce-0.88.0.dist-info}/METADATA +21 -5
  56. {aider_ce-0.87.13.dev3.dist-info → aider_ce-0.88.0.dist-info}/RECORD +59 -56
  57. {aider_ce-0.87.13.dev3.dist-info → aider_ce-0.88.0.dist-info}/WHEEL +0 -0
  58. {aider_ce-0.87.13.dev3.dist-info → aider_ce-0.88.0.dist-info}/entry_points.txt +0 -0
  59. {aider_ce-0.87.13.dev3.dist-info → aider_ce-0.88.0.dist-info}/licenses/LICENSE.txt +0 -0
  60. {aider_ce-0.87.13.dev3.dist-info → aider_ce-0.88.0.dist-info}/top_level.txt +0 -0
aider/commands.py CHANGED
@@ -1,3 +1,4 @@
1
+ import asyncio
1
2
  import glob
2
3
  import os
3
4
  import re
@@ -216,7 +217,7 @@ class Commands:
216
217
  else:
217
218
  self.io.tool_output("Please provide a partial model name to search for.")
218
219
 
219
- def cmd_web(self, args, return_content=False):
220
+ async def cmd_web(self, args, return_content=False):
220
221
  "Scrape a webpage, convert to markdown and send in a message"
221
222
 
222
223
  url = args.strip()
@@ -230,7 +231,7 @@ class Commands:
230
231
  if disable_playwright:
231
232
  res = False
232
233
  else:
233
- res = install_playwright(self.io)
234
+ res = await install_playwright(self.io)
234
235
  if not res:
235
236
  self.io.tool_warning("Unable to initialize playwright.")
236
237
 
@@ -284,7 +285,7 @@ class Commands:
284
285
 
285
286
  return commands
286
287
 
287
- def do_run(self, cmd_name, args):
288
+ async def do_run(self, cmd_name, args):
288
289
  cmd_name = cmd_name.replace("-", "_")
289
290
  cmd_method_name = f"cmd_{cmd_name}"
290
291
  cmd_method = getattr(self, cmd_method_name, None)
@@ -293,7 +294,10 @@ class Commands:
293
294
  return
294
295
 
295
296
  try:
296
- return cmd_method(args)
297
+ if asyncio.iscoroutinefunction(cmd_method):
298
+ return await cmd_method(args)
299
+ else:
300
+ return cmd_method(args)
297
301
  except ANY_GIT_ERROR as err:
298
302
  self.io.tool_error(f"Unable to complete {cmd_name}: {err}")
299
303
 
@@ -309,10 +313,10 @@ class Commands:
309
313
  matching_commands = [cmd for cmd in all_commands if cmd.startswith(first_word)]
310
314
  return matching_commands, first_word, rest_inp
311
315
 
312
- def run(self, inp):
316
+ async def run(self, inp):
313
317
  if inp.startswith("!"):
314
318
  self.coder.event("command_run")
315
- return self.do_run("run", inp[1:])
319
+ return await self.do_run("run", inp[1:])
316
320
 
317
321
  res = self.matching_commands(inp)
318
322
  if res is None:
@@ -321,11 +325,11 @@ class Commands:
321
325
  if len(matching_commands) == 1:
322
326
  command = matching_commands[0][1:]
323
327
  self.coder.event(f"command_{command}")
324
- return self.do_run(command, rest_inp)
328
+ return await self.do_run(command, rest_inp)
325
329
  elif first_word in matching_commands:
326
330
  command = first_word[1:]
327
331
  self.coder.event(f"command_{command}")
328
- return self.do_run(command, rest_inp)
332
+ return await self.do_run(command, rest_inp)
329
333
  elif len(matching_commands) > 1:
330
334
  self.io.tool_error(f"Ambiguous command: {', '.join(matching_commands)}")
331
335
  else:
@@ -334,14 +338,14 @@ class Commands:
334
338
  # any method called cmd_xxx becomes a command automatically.
335
339
  # each one must take an args param.
336
340
 
337
- def cmd_commit(self, args=None):
341
+ async def cmd_commit(self, args=None):
338
342
  "Commit edits to the repo made outside the chat (commit message optional)"
339
343
  try:
340
- self.raw_cmd_commit(args)
344
+ await self.raw_cmd_commit(args)
341
345
  except ANY_GIT_ERROR as err:
342
346
  self.io.tool_error(f"Unable to complete commit: {err}")
343
347
 
344
- def raw_cmd_commit(self, args=None):
348
+ async def raw_cmd_commit(self, args=None):
345
349
  if not self.coder.repo:
346
350
  self.io.tool_error("No git repository found.")
347
351
  return
@@ -351,9 +355,9 @@ class Commands:
351
355
  return
352
356
 
353
357
  commit_message = args.strip() if args else None
354
- self.coder.repo.commit(message=commit_message, coder=self.coder)
358
+ await self.coder.repo.commit(message=commit_message, coder=self.coder)
355
359
 
356
- def cmd_lint(self, args="", fnames=None):
360
+ async def cmd_lint(self, args="", fnames=None):
357
361
  "Lint and fix in-chat files or all dirty files if none in chat"
358
362
 
359
363
  if not self.coder.repo:
@@ -386,15 +390,15 @@ class Commands:
386
390
  continue
387
391
 
388
392
  self.io.tool_output(errors)
389
- if not self.io.confirm_ask(f"Fix lint errors in {fname}?", default="y"):
393
+ if not await self.io.confirm_ask(f"Fix lint errors in {fname}?", default="y"):
390
394
  continue
391
395
 
392
396
  # Commit everything before we start fixing lint errors
393
397
  if self.coder.repo.is_dirty() and self.coder.dirty_commits:
394
- self.cmd_commit("")
398
+ await self.cmd_commit("")
395
399
 
396
400
  if not lint_coder:
397
- lint_coder = self.coder.clone(
401
+ lint_coder = await self.coder.clone(
398
402
  # Clear the chat history, fnames
399
403
  cur_messages=[],
400
404
  done_messages=[],
@@ -402,11 +406,11 @@ class Commands:
402
406
  )
403
407
 
404
408
  lint_coder.add_rel_fname(fname)
405
- lint_coder.run(errors)
409
+ await lint_coder.run(errors)
406
410
  lint_coder.abs_fnames = set()
407
411
 
408
412
  if lint_coder and self.coder.repo.is_dirty() and self.coder.auto_commits:
409
- self.cmd_commit("")
413
+ await self.cmd_commit("")
410
414
 
411
415
  def cmd_clear(self, args):
412
416
  "Clear the chat history"
@@ -857,7 +861,7 @@ class Commands:
857
861
  res = list(map(str, matched_files))
858
862
  return res
859
863
 
860
- def cmd_add(self, args):
864
+ async def cmd_add(self, args):
861
865
  "Add files to the chat so aider can edit them or review them in detail"
862
866
 
863
867
  if not args.strip():
@@ -908,7 +912,9 @@ class Commands:
908
912
  self.io.tool_output(f"You can add to git with: /git add {fname}")
909
913
  continue
910
914
 
911
- if self.io.confirm_ask(f"No files matched '{word}'. Do you want to create {fname}?"):
915
+ if await self.io.confirm_ask(
916
+ f"No files matched '{word}'. Do you want to create {fname}?"
917
+ ):
912
918
  try:
913
919
  fname.parent.mkdir(parents=True, exist_ok=True)
914
920
  fname.touch()
@@ -1123,7 +1129,7 @@ class Commands:
1123
1129
 
1124
1130
  self.io.tool_output(combined_output)
1125
1131
 
1126
- def cmd_test(self, args):
1132
+ async def cmd_test(self, args):
1127
1133
  "Run a shell command and add the output to the chat on non-zero exit code"
1128
1134
  if not args and self.coder.test_cmd:
1129
1135
  args = self.coder.test_cmd
@@ -1134,7 +1140,7 @@ class Commands:
1134
1140
  if not callable(args):
1135
1141
  if type(args) is not str:
1136
1142
  raise ValueError(repr(args))
1137
- return self.cmd_run(args, True)
1143
+ return await self.cmd_run(args, True)
1138
1144
 
1139
1145
  errors = args()
1140
1146
  if not errors:
@@ -1143,10 +1149,14 @@ class Commands:
1143
1149
  self.io.tool_output(errors)
1144
1150
  return errors
1145
1151
 
1146
- def cmd_run(self, args, add_on_nonzero_exit=False):
1152
+ async def cmd_run(self, args, add_on_nonzero_exit=False):
1147
1153
  "Run a shell command and optionally add the output to the chat (alias: !)"
1148
- exit_status, combined_output = run_cmd(
1149
- args, verbose=self.verbose, error_print=self.io.tool_error, cwd=self.coder.root
1154
+ exit_status, combined_output = await asyncio.to_thread(
1155
+ run_cmd,
1156
+ args,
1157
+ verbose=self.verbose,
1158
+ error_print=self.io.tool_error,
1159
+ cwd=self.coder.root,
1150
1160
  )
1151
1161
 
1152
1162
  if combined_output is None:
@@ -1159,7 +1169,9 @@ class Commands:
1159
1169
  if add_on_nonzero_exit:
1160
1170
  add = exit_status != 0
1161
1171
  else:
1162
- add = self.io.confirm_ask(f"Add {k_tokens:.1f}k tokens of command output to the chat?")
1172
+ add = await self.io.confirm_ask(
1173
+ f"Add {k_tokens:.1f}k tokens of command output to the chat?"
1174
+ )
1163
1175
 
1164
1176
  if add:
1165
1177
  num_lines = len(combined_output.strip().splitlines())
@@ -1360,7 +1372,7 @@ class Commands:
1360
1372
  self.io.tool_output()
1361
1373
  self.io.tool_output("Use `/help <question>` to ask questions about how to use aider.")
1362
1374
 
1363
- def cmd_help(self, args):
1375
+ async def cmd_help(self, args):
1364
1376
  "Ask questions about aider"
1365
1377
 
1366
1378
  if not args.strip():
@@ -1378,7 +1390,7 @@ class Commands:
1378
1390
 
1379
1391
  self.help = Help()
1380
1392
 
1381
- coder = Coder.create(
1393
+ coder = await Coder.create(
1382
1394
  io=self.io,
1383
1395
  from_coder=self.coder,
1384
1396
  edit_format="help",
@@ -1393,7 +1405,7 @@ class Commands:
1393
1405
  """
1394
1406
  user_msg += "\n".join(self.coder.get_announcements()) + "\n"
1395
1407
 
1396
- coder.run(user_msg, preproc=False)
1408
+ await coder.run(user_msg, preproc=False)
1397
1409
 
1398
1410
  if self.coder.repo_map:
1399
1411
  map_tokens = self.coder.repo_map.max_map_tokens
@@ -1426,39 +1438,39 @@ class Commands:
1426
1438
  def completions_navigator(self):
1427
1439
  raise CommandCompletionException()
1428
1440
 
1429
- def cmd_ask(self, args):
1441
+ async def cmd_ask(self, args):
1430
1442
  """Ask questions about the code base without editing any files. If no prompt provided, switches to ask mode.""" # noqa
1431
- return self._generic_chat_command(args, "ask")
1443
+ return await self._generic_chat_command(args, "ask")
1432
1444
 
1433
- def cmd_code(self, args):
1445
+ async def cmd_code(self, args):
1434
1446
  """Ask for changes to your code. If no prompt provided, switches to code mode.""" # noqa
1435
- return self._generic_chat_command(args, self.coder.main_model.edit_format)
1447
+ return await self._generic_chat_command(args, self.coder.main_model.edit_format)
1436
1448
 
1437
- def cmd_architect(self, args):
1449
+ async def cmd_architect(self, args):
1438
1450
  """Enter architect/editor mode using 2 different models. If no prompt provided, switches to architect/editor mode.""" # noqa
1439
- return self._generic_chat_command(args, "architect")
1451
+ return await self._generic_chat_command(args, "architect")
1440
1452
 
1441
- def cmd_context(self, args):
1453
+ async def cmd_context(self, args):
1442
1454
  """Enter context mode to see surrounding code context. If no prompt provided, switches to context mode.""" # noqa
1443
- return self._generic_chat_command(args, "context", placeholder=args.strip() or None)
1455
+ return await self._generic_chat_command(args, "context", placeholder=args.strip() or None)
1444
1456
 
1445
- def cmd_navigator(self, args):
1457
+ async def cmd_navigator(self, args):
1446
1458
  """Enter navigator mode to autonomously discover and manage relevant files. If no prompt provided, switches to navigator mode.""" # noqa
1447
1459
  # Enable context management when entering navigator mode
1448
1460
  if hasattr(self.coder, "context_management_enabled"):
1449
1461
  self.coder.context_management_enabled = True
1450
1462
  self.io.tool_output("Context management enabled for large files")
1451
1463
 
1452
- return self._generic_chat_command(args, "navigator", placeholder=args.strip() or None)
1464
+ return await self._generic_chat_command(args, "navigator", placeholder=args.strip() or None)
1453
1465
 
1454
- def _generic_chat_command(self, args, edit_format, placeholder=None):
1466
+ async def _generic_chat_command(self, args, edit_format, placeholder=None):
1455
1467
  if not args.strip():
1456
1468
  # Switch to the corresponding chat mode if no args provided
1457
1469
  return self.cmd_chat_mode(edit_format)
1458
1470
 
1459
1471
  from aider.coders.base_coder import Coder
1460
1472
 
1461
- coder = Coder.create(
1473
+ coder = await Coder.create(
1462
1474
  io=self.io,
1463
1475
  from_coder=self.coder,
1464
1476
  edit_format=edit_format,
@@ -1467,7 +1479,7 @@ class Commands:
1467
1479
  )
1468
1480
 
1469
1481
  user_msg = args
1470
- coder.run(user_msg)
1482
+ await coder.run(user_msg)
1471
1483
 
1472
1484
  # Use the provided placeholder if any
1473
1485
  raise SwitchCoder(
@@ -1821,7 +1833,7 @@ class Commands:
1821
1833
  def completions_raw_load(self, document, complete_event):
1822
1834
  return self.completions_raw_read_only(document, complete_event)
1823
1835
 
1824
- def cmd_load(self, args):
1836
+ async def cmd_load(self, args):
1825
1837
  "Load and execute commands from a file"
1826
1838
  if not args.strip():
1827
1839
  self.io.tool_error("Please provide a filename containing commands to load.")
@@ -1844,7 +1856,7 @@ class Commands:
1844
1856
 
1845
1857
  self.io.tool_output(f"\nExecuting: {cmd}")
1846
1858
  try:
1847
- self.run(cmd)
1859
+ await self.run(cmd)
1848
1860
  except SwitchCoder:
1849
1861
  self.io.tool_error(
1850
1862
  f"Command '{cmd}' is only supported in interactive mode, skipping."
aider/history.py CHANGED
@@ -30,13 +30,13 @@ class ChatSummary:
30
30
  sized.append((tokens, msg))
31
31
  return sized
32
32
 
33
- def summarize(self, messages, depth=0):
34
- messages = self.summarize_real(messages)
33
+ async def summarize(self, messages, depth=0):
34
+ messages = await self.summarize_real(messages)
35
35
  if messages and messages[-1]["role"] != "assistant":
36
36
  messages.append(dict(role="assistant", content="Ok."))
37
37
  return messages
38
38
 
39
- def summarize_real(self, messages, depth=0):
39
+ async def summarize_real(self, messages, depth=0):
40
40
  if not self.models:
41
41
  raise ValueError("No models available for summarization")
42
42
 
@@ -48,11 +48,11 @@ class ChatSummary:
48
48
  # All fit, no summarization needed
49
49
  return messages
50
50
  # This is a chunk that's small enough to summarize in one go
51
- return self.summarize_all(messages)
51
+ return await self.summarize_all(messages)
52
52
 
53
53
  min_split = 4
54
54
  if len(messages) <= min_split or depth > 4:
55
- return self.summarize_all(messages)
55
+ return await self.summarize_all(messages)
56
56
 
57
57
  tail_tokens = 0
58
58
  split_index = len(messages)
@@ -78,13 +78,13 @@ class ChatSummary:
78
78
  split_index -= 1
79
79
 
80
80
  if split_index <= min_split:
81
- return self.summarize_all(messages)
81
+ return await self.summarize_all(messages)
82
82
 
83
83
  # Split head and tail
84
84
  head = messages[:split_index]
85
85
  tail = messages[split_index:]
86
86
 
87
- summary = self.summarize_real(head, depth + 1)
87
+ summary = await self.summarize_real(head, depth + 1)
88
88
 
89
89
  # If the combined summary and tail still fits, return directly
90
90
  new_messages = summary + tail
@@ -96,9 +96,9 @@ class ChatSummary:
96
96
  return new_messages
97
97
 
98
98
  # Otherwise recurse with increased depth
99
- return self.summarize_real(new_messages, depth + 1)
99
+ return await self.summarize_real(new_messages, depth + 1)
100
100
 
101
- def summarize_all(self, messages):
101
+ async def summarize_all(self, messages):
102
102
  content = ""
103
103
  for msg in messages:
104
104
  role = msg["role"].upper()
@@ -118,7 +118,7 @@ class ChatSummary:
118
118
 
119
119
  for model in self.models:
120
120
  try:
121
- summary = model.simple_send_with_retries(summarize_messages)
121
+ summary = await model.simple_send_with_retries(summarize_messages)
122
122
  if summary is not None:
123
123
  summary = prompts.summary_prefix + summary
124
124
  return [dict(role="user", content=summary)]
@@ -129,7 +129,7 @@ class ChatSummary:
129
129
  print(err)
130
130
  raise ValueError(err)
131
131
 
132
- def summarize_all_as_text(self, messages, prompt, max_tokens=None):
132
+ async def summarize_all_as_text(self, messages, prompt, max_tokens=None):
133
133
  content = ""
134
134
  for msg in messages:
135
135
  role = msg["role"].upper()
@@ -149,7 +149,9 @@ class ChatSummary:
149
149
 
150
150
  for model in self.models:
151
151
  try:
152
- summary = model.simple_send_with_retries(summarize_messages, max_tokens=max_tokens)
152
+ summary = await model.simple_send_with_retries(
153
+ summarize_messages, max_tokens=max_tokens
154
+ )
153
155
  if summary is not None:
154
156
  return summary
155
157
  except Exception as e: