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/tools/grep.py CHANGED
@@ -1,9 +1,54 @@
1
- import shlex
2
1
  import shutil
3
2
  from pathlib import Path
4
3
 
4
+ import oslex
5
+
5
6
  from aider.run_cmd import run_cmd_subprocess
6
7
 
8
+ grep_schema = {
9
+ "type": "function",
10
+ "function": {
11
+ "name": "Grep",
12
+ "description": "Search for a pattern in files.",
13
+ "parameters": {
14
+ "type": "object",
15
+ "properties": {
16
+ "pattern": {
17
+ "type": "string",
18
+ "description": "The pattern to search for.",
19
+ },
20
+ "file_pattern": {
21
+ "type": "string",
22
+ "description": "Glob pattern for files to search. Defaults to '*'.",
23
+ },
24
+ "directory": {
25
+ "type": "string",
26
+ "description": "Directory to search in. Defaults to '.'.",
27
+ },
28
+ "use_regex": {
29
+ "type": "boolean",
30
+ "description": "Whether to use regex. Defaults to False.",
31
+ },
32
+ "case_insensitive": {
33
+ "type": "boolean",
34
+ "description": (
35
+ "Whether to perform a case-insensitive search. Defaults to False."
36
+ ),
37
+ },
38
+ "context_before": {
39
+ "type": "integer",
40
+ "description": "Number of lines to show before a match. Defaults to 5.",
41
+ },
42
+ "context_after": {
43
+ "type": "integer",
44
+ "description": "Number of lines to show after a match. Defaults to 5.",
45
+ },
46
+ },
47
+ "required": ["pattern"],
48
+ },
49
+ },
50
+ }
51
+
7
52
 
8
53
  def _find_search_tool():
9
54
  """Find the best available command-line search tool (rg, ag, grep)."""
@@ -117,7 +162,7 @@ def _execute_grep(
117
162
  cmd_args.extend([pattern, str(search_dir_path)])
118
163
 
119
164
  # Convert list to command string for run_cmd_subprocess
120
- command_string = shlex.join(cmd_args)
165
+ command_string = oslex.join(cmd_args)
121
166
 
122
167
  coder.io.tool_output(f"⚙️ Executing {tool_name}: {command_string}")
123
168
 
@@ -10,6 +10,29 @@ from .tool_utils import (
10
10
  validate_file_for_edit,
11
11
  )
12
12
 
13
+ indent_lines_schema = {
14
+ "type": "function",
15
+ "function": {
16
+ "name": "IndentLines",
17
+ "description": "Indent a block of lines in a file.",
18
+ "parameters": {
19
+ "type": "object",
20
+ "properties": {
21
+ "file_path": {"type": "string"},
22
+ "start_pattern": {"type": "string"},
23
+ "end_pattern": {"type": "string"},
24
+ "line_count": {"type": "integer"},
25
+ "indent_levels": {"type": "integer", "default": 1},
26
+ "near_context": {"type": "string"},
27
+ "occurrence": {"type": "integer", "default": 1},
28
+ "change_id": {"type": "string"},
29
+ "dry_run": {"type": "boolean", "default": False},
30
+ },
31
+ "required": ["file_path", "start_pattern"],
32
+ },
33
+ },
34
+ }
35
+
13
36
 
14
37
  def _execute_indent_lines(
15
38
  coder,
@@ -138,6 +161,8 @@ def _execute_indent_lines(
138
161
  change_id,
139
162
  )
140
163
 
164
+ coder.files_edited_by_tools.add(rel_path)
165
+
141
166
  # 8. Format and return result
142
167
  action_past = "Indented" if indent_levels > 0 else "Unindented"
143
168
  success_message = (
@@ -12,6 +12,30 @@ from .tool_utils import (
12
12
  validate_file_for_edit,
13
13
  )
14
14
 
15
+ insert_block_schema = {
16
+ "type": "function",
17
+ "function": {
18
+ "name": "InsertBlock",
19
+ "description": "Insert a block of content into a file.",
20
+ "parameters": {
21
+ "type": "object",
22
+ "properties": {
23
+ "file_path": {"type": "string"},
24
+ "content": {"type": "string"},
25
+ "after_pattern": {"type": "string"},
26
+ "before_pattern": {"type": "string"},
27
+ "occurrence": {"type": "integer", "default": 1},
28
+ "change_id": {"type": "string"},
29
+ "dry_run": {"type": "boolean", "default": False},
30
+ "position": {"type": "string", "enum": ["top", "bottom"]},
31
+ "auto_indent": {"type": "boolean", "default": True},
32
+ "use_regex": {"type": "boolean", "default": False},
33
+ },
34
+ "required": ["file_path", "content"],
35
+ },
36
+ },
37
+ }
38
+
15
39
 
16
40
  def _execute_insert_block(
17
41
  coder,
@@ -187,6 +211,8 @@ def _execute_insert_block(
187
211
  change_id,
188
212
  )
189
213
 
214
+ coder.files_edited_by_tools.add(rel_path)
215
+
190
216
  # 9. Format and return result
191
217
  if position:
192
218
  success_message = f"Inserted block {pattern_type} {file_path}"
@@ -1,6 +1,21 @@
1
1
  import traceback
2
2
  from datetime import datetime
3
3
 
4
+ list_changes_schema = {
5
+ "type": "function",
6
+ "function": {
7
+ "name": "ListChanges",
8
+ "description": "List recent changes made.",
9
+ "parameters": {
10
+ "type": "object",
11
+ "properties": {
12
+ "file_path": {"type": "string"},
13
+ "limit": {"type": "integer", "default": 10},
14
+ },
15
+ },
16
+ },
17
+ }
18
+
4
19
 
5
20
  def _execute_list_changes(coder, file_path=None, limit=10):
6
21
  """
aider/tools/ls.py CHANGED
@@ -1,7 +1,30 @@
1
1
  import os
2
2
 
3
+ ls_schema = {
4
+ "type": "function",
5
+ "function": {
6
+ "name": "Ls",
7
+ "description": "List files in a directory.",
8
+ "parameters": {
9
+ "type": "object",
10
+ "properties": {
11
+ "directory": {
12
+ "type": "string",
13
+ "description": "The directory to list.",
14
+ },
15
+ },
16
+ "required": ["directory"],
17
+ },
18
+ },
19
+ }
3
20
 
4
- def execute_ls(coder, dir_path):
21
+
22
+ def execute_ls(coder, dir_path=None, directory=None):
23
+ # Handle both positional and keyword arguments for backward compatibility
24
+ if dir_path is None and directory is not None:
25
+ dir_path = directory
26
+ elif dir_path is None:
27
+ return "Error: Missing directory parameter"
5
28
  """
6
29
  List files in directory and optionally add some to context.
7
30
 
@@ -1,5 +1,23 @@
1
1
  import os
2
2
 
3
+ make_editable_schema = {
4
+ "type": "function",
5
+ "function": {
6
+ "name": "MakeEditable",
7
+ "description": "Make a read-only file editable.",
8
+ "parameters": {
9
+ "type": "object",
10
+ "properties": {
11
+ "file_path": {
12
+ "type": "string",
13
+ "description": "The path to the file to make editable.",
14
+ },
15
+ },
16
+ "required": ["file_path"],
17
+ },
18
+ },
19
+ }
20
+
3
21
 
4
22
  # Keep the underscore prefix as this function is primarily for internal coder use
5
23
  def _execute_make_editable(coder, file_path):
@@ -1,3 +1,22 @@
1
+ make_readonly_schema = {
2
+ "type": "function",
3
+ "function": {
4
+ "name": "MakeReadonly",
5
+ "description": "Make an editable file read-only.",
6
+ "parameters": {
7
+ "type": "object",
8
+ "properties": {
9
+ "file_path": {
10
+ "type": "string",
11
+ "description": "The path to the file to make read-only.",
12
+ },
13
+ },
14
+ "required": ["file_path"],
15
+ },
16
+ },
17
+ }
18
+
19
+
1
20
  def _execute_make_readonly(coder, file_path):
2
21
  """
3
22
  Convert an editable file to a read-only file.
aider/tools/remove.py CHANGED
@@ -1,5 +1,27 @@
1
1
  import time
2
2
 
3
+ remove_schema = {
4
+ "type": "function",
5
+ "function": {
6
+ "name": "Remove",
7
+ "description": (
8
+ "Remove a file from the chat context. Should be used proactively to keep con"
9
+ "Should be used after editing a file when all edits are done "
10
+ "and the file is no longer necessary in context."
11
+ ),
12
+ "parameters": {
13
+ "type": "object",
14
+ "properties": {
15
+ "file_path": {
16
+ "type": "string",
17
+ "description": "The path to the file to remove.",
18
+ },
19
+ },
20
+ "required": ["file_path"],
21
+ },
22
+ },
23
+ }
24
+
3
25
 
4
26
  def _execute_remove(coder, file_path):
5
27
  """
@@ -7,6 +7,25 @@ from .tool_utils import (
7
7
  validate_file_for_edit,
8
8
  )
9
9
 
10
+ replace_all_schema = {
11
+ "type": "function",
12
+ "function": {
13
+ "name": "ReplaceAll",
14
+ "description": "Replace all occurrences of text in a file.",
15
+ "parameters": {
16
+ "type": "object",
17
+ "properties": {
18
+ "file_path": {"type": "string"},
19
+ "find_text": {"type": "string"},
20
+ "replace_text": {"type": "string"},
21
+ "change_id": {"type": "string"},
22
+ "dry_run": {"type": "boolean", "default": False},
23
+ },
24
+ "required": ["file_path", "find_text", "replace_text"],
25
+ },
26
+ },
27
+ }
28
+
10
29
 
11
30
  def _execute_replace_all(coder, file_path, find_text, replace_text, change_id=None, dry_run=False):
12
31
  """
@@ -63,6 +82,8 @@ def _execute_replace_all(coder, file_path, find_text, replace_text, change_id=No
63
82
  change_id,
64
83
  )
65
84
 
85
+ coder.files_edited_by_tools.add(rel_path)
86
+
66
87
  # 7. Format and return result
67
88
  success_message = f"Replaced {count} occurrences in {file_path}"
68
89
  return format_tool_result(
@@ -1,6 +1,25 @@
1
1
  import os
2
2
  import traceback
3
3
 
4
+ replace_line_schema = {
5
+ "type": "function",
6
+ "function": {
7
+ "name": "ReplaceLine",
8
+ "description": "Replace a single line in a file.",
9
+ "parameters": {
10
+ "type": "object",
11
+ "properties": {
12
+ "file_path": {"type": "string"},
13
+ "line_number": {"type": "integer"},
14
+ "new_content": {"type": "string"},
15
+ "change_id": {"type": "string"},
16
+ "dry_run": {"type": "boolean", "default": False},
17
+ },
18
+ "required": ["file_path", "line_number", "new_content"],
19
+ },
20
+ },
21
+ }
22
+
4
23
 
5
24
  def _execute_replace_line(
6
25
  coder, file_path, line_number, new_content, change_id=None, dry_run=False
@@ -112,7 +131,7 @@ def _execute_replace_line(
112
131
  coder.io.tool_error(f"Error tracking change for ReplaceLine: {track_e}")
113
132
  change_id = "TRACKING_FAILED"
114
133
 
115
- coder.aider_edited_files.add(rel_path)
134
+ coder.files_edited_by_tools.add(rel_path)
116
135
 
117
136
  # Improve feedback
118
137
  coder.io.tool_output(
@@ -8,6 +8,26 @@ from .tool_utils import (
8
8
  handle_tool_error,
9
9
  )
10
10
 
11
+ replace_lines_schema = {
12
+ "type": "function",
13
+ "function": {
14
+ "name": "ReplaceLines",
15
+ "description": "Replace a range of lines in a file.",
16
+ "parameters": {
17
+ "type": "object",
18
+ "properties": {
19
+ "file_path": {"type": "string"},
20
+ "start_line": {"type": "integer"},
21
+ "end_line": {"type": "integer"},
22
+ "new_content": {"type": "string"},
23
+ "change_id": {"type": "string"},
24
+ "dry_run": {"type": "boolean", "default": False},
25
+ },
26
+ "required": ["file_path", "start_line", "end_line", "new_content"],
27
+ },
28
+ },
29
+ }
30
+
11
31
 
12
32
  def _execute_replace_lines(
13
33
  coder, file_path, start_line, end_line, new_content, change_id=None, dry_run=False
@@ -139,7 +159,7 @@ def _execute_replace_lines(
139
159
  change_id,
140
160
  )
141
161
 
142
- coder.aider_edited_files.add(rel_path)
162
+ coder.files_edited_by_tools.add(rel_path)
143
163
  replaced_count = end_line - start_line + 1
144
164
  new_count = len(new_lines)
145
165
 
@@ -7,6 +7,27 @@ from .tool_utils import (
7
7
  validate_file_for_edit,
8
8
  )
9
9
 
10
+ replace_text_schema = {
11
+ "type": "function",
12
+ "function": {
13
+ "name": "ReplaceText",
14
+ "description": "Replace text in a file.",
15
+ "parameters": {
16
+ "type": "object",
17
+ "properties": {
18
+ "file_path": {"type": "string"},
19
+ "find_text": {"type": "string"},
20
+ "replace_text": {"type": "string"},
21
+ "near_context": {"type": "string"},
22
+ "occurrence": {"type": "integer", "default": 1},
23
+ "change_id": {"type": "string"},
24
+ "dry_run": {"type": "boolean", "default": False},
25
+ },
26
+ "required": ["file_path", "find_text", "replace_text"],
27
+ },
28
+ },
29
+ }
30
+
10
31
 
11
32
  def _execute_replace_text(
12
33
  coder,
@@ -111,6 +132,7 @@ def _execute_replace_text(
111
132
  change_id,
112
133
  )
113
134
 
135
+ coder.files_edited_by_tools.add(rel_path)
114
136
  # 8. Format and return result
115
137
  success_message = f"Replaced {occurrence_str} in {file_path}"
116
138
  return format_tool_result(
@@ -2,6 +2,24 @@ import os
2
2
 
3
3
  from .tool_utils import ToolError, handle_tool_error, resolve_paths
4
4
 
5
+ show_numbered_context_schema = {
6
+ "type": "function",
7
+ "function": {
8
+ "name": "ShowNumberedContext",
9
+ "description": "Show numbered lines of context around a pattern or line number.",
10
+ "parameters": {
11
+ "type": "object",
12
+ "properties": {
13
+ "file_path": {"type": "string"},
14
+ "pattern": {"type": "string"},
15
+ "line_number": {"type": "integer"},
16
+ "context_lines": {"type": "integer", "default": 3},
17
+ },
18
+ "required": ["file_path"],
19
+ },
20
+ },
21
+ }
22
+
5
23
 
6
24
  def execute_show_numbered_context(
7
25
  coder, file_path, pattern=None, line_number=None, context_lines=3
@@ -1,5 +1,20 @@
1
1
  import traceback
2
2
 
3
+ undo_change_schema = {
4
+ "type": "function",
5
+ "function": {
6
+ "name": "UndoChange",
7
+ "description": "Undo a previously applied change.",
8
+ "parameters": {
9
+ "type": "object",
10
+ "properties": {
11
+ "change_id": {"type": "string"},
12
+ "file_path": {"type": "string"},
13
+ },
14
+ },
15
+ },
16
+ }
17
+
3
18
 
4
19
  def _execute_undo_change(coder, change_id=None, file_path=None):
5
20
  """
@@ -0,0 +1,131 @@
1
+ from .tool_utils import (
2
+ ToolError,
3
+ format_tool_result,
4
+ generate_unified_diff_snippet,
5
+ handle_tool_error,
6
+ )
7
+
8
+ update_todo_list_schema = {
9
+ "type": "function",
10
+ "function": {
11
+ "name": "UpdateTodoList",
12
+ "description": "Update the todo list with new items or modify existing ones.",
13
+ "parameters": {
14
+ "type": "object",
15
+ "properties": {
16
+ "content": {
17
+ "type": "string",
18
+ "description": "The new content for the todo list.",
19
+ },
20
+ "append": {
21
+ "type": "boolean",
22
+ "description": (
23
+ "Whether to append to existing content instead of replacing it. Defaults to"
24
+ " False."
25
+ ),
26
+ },
27
+ "change_id": {
28
+ "type": "string",
29
+ "description": "Optional change ID for tracking.",
30
+ },
31
+ "dry_run": {
32
+ "type": "boolean",
33
+ "description": (
34
+ "Whether to perform a dry run without actually updating the file. Defaults"
35
+ " to False."
36
+ ),
37
+ },
38
+ },
39
+ "required": ["content"],
40
+ },
41
+ },
42
+ }
43
+
44
+
45
+ def _execute_update_todo_list(coder, content, append=False, change_id=None, dry_run=False):
46
+ """
47
+ Update the todo list file (.aider.todo.txt) with new content.
48
+ Can either replace the entire content or append to it.
49
+ """
50
+ tool_name = "UpdateTodoList"
51
+ try:
52
+ # Define the todo file path
53
+ todo_file_path = ".aider.todo.txt"
54
+ abs_path = coder.abs_root_path(todo_file_path)
55
+
56
+ # Get existing content if appending
57
+ existing_content = ""
58
+ import os
59
+
60
+ if os.path.isfile(abs_path):
61
+ existing_content = coder.io.read_text(abs_path) or ""
62
+
63
+ # Prepare new content
64
+ if append:
65
+ if existing_content and not existing_content.endswith("\n"):
66
+ existing_content += "\n"
67
+ new_content = existing_content + content
68
+ else:
69
+ new_content = content
70
+
71
+ # Check if content exceeds 4096 characters and warn
72
+ if len(new_content) > 4096:
73
+ coder.io.tool_warning(
74
+ "⚠️ Todo list content exceeds 4096 characters. Consider summarizing the plan before"
75
+ " proceeding."
76
+ )
77
+
78
+ # Check if content actually changed
79
+ if existing_content == new_content:
80
+ coder.io.tool_warning("No changes made: new content is identical to existing")
81
+ return "Warning: No changes made (content identical to existing)"
82
+
83
+ # Generate diff for feedback
84
+ diff_snippet = generate_unified_diff_snippet(existing_content, new_content, todo_file_path)
85
+
86
+ # Handle dry run
87
+ if dry_run:
88
+ action = "append to" if append else "replace"
89
+ dry_run_message = f"Dry run: Would {action} todo list in {todo_file_path}."
90
+ return format_tool_result(
91
+ coder,
92
+ tool_name,
93
+ "",
94
+ dry_run=True,
95
+ dry_run_message=dry_run_message,
96
+ diff_snippet=diff_snippet,
97
+ )
98
+
99
+ # Apply change
100
+ metadata = {
101
+ "append": append,
102
+ "existing_length": len(existing_content),
103
+ "new_length": len(new_content),
104
+ }
105
+
106
+ # Write the file directly since it's a special file
107
+ coder.io.write_text(abs_path, new_content)
108
+
109
+ # Track the change
110
+ final_change_id = coder.change_tracker.track_change(
111
+ file_path=todo_file_path,
112
+ change_type="updatetodolist",
113
+ original_content=existing_content,
114
+ new_content=new_content,
115
+ metadata=metadata,
116
+ change_id=change_id,
117
+ )
118
+
119
+ coder.aider_edited_files.add(todo_file_path)
120
+
121
+ # Format and return result
122
+ action = "appended to" if append else "updated"
123
+ success_message = f"Successfully {action} todo list in {todo_file_path}"
124
+ return format_tool_result(
125
+ coder, tool_name, success_message, change_id=final_change_id, diff_snippet=diff_snippet
126
+ )
127
+
128
+ except ToolError as e:
129
+ return handle_tool_error(coder, tool_name, e, add_traceback=False)
130
+ except Exception as e:
131
+ return handle_tool_error(coder, tool_name, e)
aider/tools/view.py CHANGED
@@ -1,3 +1,26 @@
1
+ view_schema = {
2
+ "type": "function",
3
+ "function": {
4
+ "name": "View",
5
+ "description": (
6
+ "View a specific file and add it to context."
7
+ "Only use this when the file is not already in the context "
8
+ "and when editing the file is necessary to accomplish the goal."
9
+ ),
10
+ "parameters": {
11
+ "type": "object",
12
+ "properties": {
13
+ "file_path": {
14
+ "type": "string",
15
+ "description": "The path to the file to view.",
16
+ },
17
+ },
18
+ "required": ["file_path"],
19
+ },
20
+ },
21
+ }
22
+
23
+
1
24
  def execute_view(coder, file_path):
2
25
  """
3
26
  Explicitly add a file to context as read-only.