aider-ce 0.87.13.dev3__py3-none-any.whl → 0.88.1__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 (61) 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 +511 -190
  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/exceptions.py +1 -0
  15. aider/history.py +14 -12
  16. aider/io.py +354 -117
  17. aider/llm.py +12 -4
  18. aider/main.py +32 -29
  19. aider/mcp/__init__.py +65 -2
  20. aider/mcp/server.py +37 -11
  21. aider/models.py +45 -20
  22. aider/onboarding.py +5 -5
  23. aider/repo.py +7 -7
  24. aider/resources/model-metadata.json +8 -8
  25. aider/scrape.py +2 -2
  26. aider/sendchat.py +185 -15
  27. aider/tools/__init__.py +44 -23
  28. aider/tools/command.py +18 -0
  29. aider/tools/command_interactive.py +18 -0
  30. aider/tools/delete_block.py +23 -0
  31. aider/tools/delete_line.py +19 -1
  32. aider/tools/delete_lines.py +20 -1
  33. aider/tools/extract_lines.py +25 -2
  34. aider/tools/git.py +142 -0
  35. aider/tools/grep.py +47 -2
  36. aider/tools/indent_lines.py +25 -0
  37. aider/tools/insert_block.py +26 -0
  38. aider/tools/list_changes.py +15 -0
  39. aider/tools/ls.py +24 -1
  40. aider/tools/make_editable.py +18 -0
  41. aider/tools/make_readonly.py +19 -0
  42. aider/tools/remove.py +22 -0
  43. aider/tools/replace_all.py +21 -0
  44. aider/tools/replace_line.py +20 -1
  45. aider/tools/replace_lines.py +21 -1
  46. aider/tools/replace_text.py +22 -0
  47. aider/tools/show_numbered_context.py +18 -0
  48. aider/tools/undo_change.py +15 -0
  49. aider/tools/update_todo_list.py +131 -0
  50. aider/tools/view.py +23 -0
  51. aider/tools/view_files_at_glob.py +32 -27
  52. aider/tools/view_files_matching.py +51 -37
  53. aider/tools/view_files_with_symbol.py +41 -54
  54. aider/tools/view_todo_list.py +57 -0
  55. aider/waiting.py +20 -203
  56. {aider_ce-0.87.13.dev3.dist-info → aider_ce-0.88.1.dist-info}/METADATA +21 -5
  57. {aider_ce-0.87.13.dev3.dist-info → aider_ce-0.88.1.dist-info}/RECORD +60 -57
  58. {aider_ce-0.87.13.dev3.dist-info → aider_ce-0.88.1.dist-info}/WHEEL +0 -0
  59. {aider_ce-0.87.13.dev3.dist-info → aider_ce-0.88.1.dist-info}/entry_points.txt +0 -0
  60. {aider_ce-0.87.13.dev3.dist-info → aider_ce-0.88.1.dist-info}/licenses/LICENSE.txt +0 -0
  61. {aider_ce-0.87.13.dev3.dist-info → aider_ce-0.88.1.dist-info}/top_level.txt +0 -0
@@ -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.
@@ -1,10 +1,28 @@
1
1
  import fnmatch
2
2
  import os
3
3
 
4
+ view_files_at_glob_schema = {
5
+ "type": "function",
6
+ "function": {
7
+ "name": "ViewFilesAtGlob",
8
+ "description": "View files matching a glob pattern.",
9
+ "parameters": {
10
+ "type": "object",
11
+ "properties": {
12
+ "pattern": {
13
+ "type": "string",
14
+ "description": "The glob pattern to match files.",
15
+ },
16
+ },
17
+ "required": ["pattern"],
18
+ },
19
+ },
20
+ }
21
+
4
22
 
5
23
  def execute_view_files_at_glob(coder, pattern):
6
24
  """
7
- Execute a glob pattern and add matching files to context as read-only.
25
+ Execute a glob pattern and return matching files as text.
8
26
 
9
27
  This tool helps the LLM find files by pattern matching, similar to
10
28
  how a developer would use glob patterns to find files.
@@ -25,38 +43,25 @@ def execute_view_files_at_glob(coder, pattern):
25
43
  if fnmatch.fnmatch(file, pattern):
26
44
  matching_files.append(file)
27
45
 
28
- # Limit the number of files added if there are too many matches
29
- if len(matching_files) > coder.max_files_per_glob:
30
- coder.io.tool_output(
31
- f"⚠️ Found {len(matching_files)} files matching '{pattern}', "
32
- f"limiting to {coder.max_files_per_glob} most relevant files."
33
- )
34
- # Sort by modification time (most recent first)
35
- matching_files.sort(
36
- key=lambda f: os.path.getmtime(coder.abs_root_path(f)), reverse=True
37
- )
38
- matching_files = matching_files[: coder.max_files_per_glob]
39
-
40
- # Add files to context
41
- for file in matching_files:
42
- # Use the coder's internal method to add files
43
- coder._add_file_to_context(file)
44
-
45
- # Return a user-friendly result
46
+ # Return formatted text instead of adding to context
46
47
  if matching_files:
47
48
  if len(matching_files) > 10:
48
- brief = ", ".join(matching_files[:5]) + f", and {len(matching_files) - 5} more"
49
- coder.io.tool_output(
50
- f"📂 Added {len(matching_files)} files matching '{pattern}': {brief}"
49
+ result = (
50
+ f"Found {len(matching_files)} files matching '{pattern}':"
51
+ f" {', '.join(matching_files[:10])} and {len(matching_files) - 10} more"
51
52
  )
53
+ coder.io.tool_output(f"📂 Found {len(matching_files)} files matching '{pattern}'")
52
54
  else:
55
+ result = (
56
+ f"Found {len(matching_files)} files matching '{pattern}':"
57
+ f" {', '.join(matching_files)}"
58
+ )
53
59
  coder.io.tool_output(
54
- f"📂 Added files matching '{pattern}': {', '.join(matching_files)}"
60
+ f"📂 Found files matching '{pattern}':"
61
+ f" {', '.join(matching_files[:5])}{' and more' if len(matching_files) > 5 else ''}"
55
62
  )
56
- return (
57
- f"Added {len(matching_files)} files:"
58
- f" {', '.join(matching_files[:5])}{' and more' if len(matching_files) > 5 else ''}"
59
- )
63
+
64
+ return result
60
65
  else:
61
66
  coder.io.tool_output(f"⚠️ No files found matching '{pattern}'")
62
67
  return f"No files found matching '{pattern}'"
@@ -1,18 +1,46 @@
1
1
  import fnmatch
2
2
  import re
3
3
 
4
+ view_files_matching_schema = {
5
+ "type": "function",
6
+ "function": {
7
+ "name": "ViewFilesMatching",
8
+ "description": "View files containing a specific pattern.",
9
+ "parameters": {
10
+ "type": "object",
11
+ "properties": {
12
+ "pattern": {
13
+ "type": "string",
14
+ "description": "The pattern to search for in file contents.",
15
+ },
16
+ "file_pattern": {
17
+ "type": "string",
18
+ "description": "An optional glob pattern to filter which files are searched.",
19
+ },
20
+ "regex": {
21
+ "type": "boolean",
22
+ "description": (
23
+ "Whether the pattern is a regular expression. Defaults to False."
24
+ ),
25
+ },
26
+ },
27
+ "required": ["pattern"],
28
+ },
29
+ },
30
+ }
4
31
 
5
- def execute_view_files_matching(coder, search_pattern, file_pattern=None, regex=False):
32
+
33
+ def execute_view_files_matching(coder, pattern, file_pattern=None, regex=False):
6
34
  """
7
- Search for pattern (literal string or regex) in files and add matching files to context as read-only.
35
+ Search for pattern (literal string or regex) in files and return matching files as text.
8
36
 
9
37
  Args:
10
38
  coder: The Coder instance.
11
- search_pattern (str): The pattern to search for.
39
+ pattern (str): The pattern to search for.
12
40
  Treated as a literal string by default.
13
41
  file_pattern (str, optional): Glob pattern to filter which files are searched.
14
42
  Defaults to None (search all files).
15
- regex (bool, optional): If True, treat search_pattern as a regular expression.
43
+ regex (bool, optional): If True, treat pattern as a regular expression.
16
44
  Defaults to False.
17
45
 
18
46
  This tool lets the LLM search for content within files, mimicking
@@ -29,9 +57,7 @@ def execute_view_files_matching(coder, search_pattern, file_pattern=None, regex=
29
57
  files_to_search.append(file)
30
58
 
31
59
  if not files_to_search:
32
- return (
33
- f"No files matching '{file_pattern}' to search for pattern '{search_pattern}'"
34
- )
60
+ return f"No files matching '{file_pattern}' to search for pattern '{pattern}'"
35
61
  else:
36
62
  # Search all files if no pattern provided
37
63
  files_to_search = coder.get_all_relative_files()
@@ -46,16 +72,16 @@ def execute_view_files_matching(coder, search_pattern, file_pattern=None, regex=
46
72
  match_count = 0
47
73
  if regex:
48
74
  try:
49
- matches_found = re.findall(search_pattern, content)
75
+ matches_found = re.findall(pattern, content)
50
76
  match_count = len(matches_found)
51
77
  except re.error as e:
52
78
  # Handle invalid regex patterns gracefully
53
- coder.io.tool_error(f"Invalid regex pattern '{search_pattern}': {e}")
79
+ coder.io.tool_error(f"Invalid regex pattern '{pattern}': {e}")
54
80
  # Skip this file for this search if regex is invalid
55
81
  continue
56
82
  else:
57
83
  # Exact string matching
58
- match_count = content.count(search_pattern)
84
+ match_count = content.count(pattern)
59
85
 
60
86
  if match_count > 0:
61
87
  matches[file] = match_count
@@ -63,40 +89,28 @@ def execute_view_files_matching(coder, search_pattern, file_pattern=None, regex=
63
89
  # Skip files that can't be read (binary, etc.)
64
90
  pass
65
91
 
66
- # Limit the number of files added if there are too many matches
67
- if len(matches) > coder.max_files_per_glob:
68
- coder.io.tool_output(
69
- f"⚠️ Found '{search_pattern}' in {len(matches)} files, "
70
- f"limiting to {coder.max_files_per_glob} files with most matches."
71
- )
72
- # Sort by number of matches (most matches first)
73
- sorted_matches = sorted(matches.items(), key=lambda x: x[1], reverse=True)
74
- matches = dict(sorted_matches[: coder.max_files_per_glob])
75
-
76
- # Add matching files to context
77
- for file in matches:
78
- coder._add_file_to_context(file)
79
-
80
- # Return a user-friendly result
92
+ # Return formatted text instead of adding to context
81
93
  if matches:
82
94
  # Sort by number of matches (most matches first)
83
95
  sorted_matches = sorted(matches.items(), key=lambda x: x[1], reverse=True)
84
- match_list = [f"{file} ({count} matches)" for file, count in sorted_matches[:5]]
96
+ match_list = [f"{file} ({count} matches)" for file, count in sorted_matches]
85
97
 
86
- if len(sorted_matches) > 5:
87
- coder.io.tool_output(
88
- f"🔍 Found '{search_pattern}' in {len(matches)} files:"
89
- f" {', '.join(match_list)} and {len(matches) - 5} more"
90
- )
91
- return (
92
- f"Found in {len(matches)} files: {', '.join(match_list)} and"
93
- f" {len(matches) - 5} more"
98
+ if len(matches) > 10:
99
+ result = (
100
+ f"Found '{pattern}' in {len(matches)} files: {', '.join(match_list[:10])} and"
101
+ f" {len(matches) - 10} more"
94
102
  )
103
+ coder.io.tool_output(f"🔍 Found '{pattern}' in {len(matches)} files")
95
104
  else:
96
- coder.io.tool_output(f"🔍 Found '{search_pattern}' in: {', '.join(match_list)}")
97
- return f"Found in {len(matches)} files: {', '.join(match_list)}"
105
+ result = f"Found '{pattern}' in {len(matches)} files: {', '.join(match_list)}"
106
+ coder.io.tool_output(
107
+ f"🔍 Found '{pattern}' in:"
108
+ f" {', '.join(match_list[:5])}{' and more' if len(matches) > 5 else ''}"
109
+ )
110
+
111
+ return result
98
112
  else:
99
- coder.io.tool_output(f"⚠️ Pattern '{search_pattern}' not found in any files")
113
+ coder.io.tool_output(f"⚠️ Pattern '{pattern}' not found in any files")
100
114
  return "Pattern not found in any files"
101
115
  except Exception as e:
102
116
  coder.io.tool_error(f"Error in ViewFilesMatching: {str(e)}")
@@ -1,9 +1,25 @@
1
- import os
1
+ view_files_with_symbol_schema = {
2
+ "type": "function",
3
+ "function": {
4
+ "name": "ViewFilesWithSymbol",
5
+ "description": "View files that contain a specific symbol (e.g., class, function).",
6
+ "parameters": {
7
+ "type": "object",
8
+ "properties": {
9
+ "symbol": {
10
+ "type": "string",
11
+ "description": "The symbol to search for.",
12
+ },
13
+ },
14
+ "required": ["symbol"],
15
+ },
16
+ },
17
+ }
2
18
 
3
19
 
4
20
  def _execute_view_files_with_symbol(coder, symbol):
5
21
  """
6
- Find files containing a symbol using RepoMap and add them to context.
22
+ Find files containing a symbol using RepoMap and return them as text.
7
23
  Checks files already in context first.
8
24
  """
9
25
  if not coder.repo_map:
@@ -13,7 +29,6 @@ def _execute_view_files_with_symbol(coder, symbol):
13
29
  if not symbol:
14
30
  return "Error: Missing 'symbol' parameter for ViewFilesWithSymbol"
15
31
 
16
- # --- Start Modification ---
17
32
  # 1. Check files already in context
18
33
  files_in_context = list(coder.abs_fnames) + list(coder.abs_read_only_fnames)
19
34
  found_in_context = []
@@ -34,20 +49,11 @@ def _execute_view_files_with_symbol(coder, symbol):
34
49
  if found_in_context:
35
50
  # Symbol found in already loaded files. Report this and stop.
36
51
  file_list = ", ".join(sorted(list(set(found_in_context))))
37
- coder.io.tool_output(
38
- f"Symbol '{symbol}' found in already loaded file(s): {file_list}. No external search"
39
- " performed."
40
- )
41
- return (
42
- f"Symbol '{symbol}' found in already loaded file(s): {file_list}. No external search"
43
- " performed."
44
- )
45
- # --- End Modification ---
52
+ coder.io.tool_output(f"Symbol '{symbol}' found in already loaded file(s): {file_list}")
53
+ return f"Symbol '{symbol}' found in already loaded file(s): {file_list}"
46
54
 
47
55
  # 2. If not found in context, search the repository using RepoMap
48
- coder.io.tool_output(
49
- f"🔎 Searching for symbol '{symbol}' in repository (excluding current context)..."
50
- )
56
+ coder.io.tool_output(f"🔎 Searching for symbol '{symbol}' in repository...")
51
57
  try:
52
58
  found_files = set()
53
59
  current_context_files = coder.abs_fnames | coder.abs_read_only_fnames
@@ -71,50 +77,31 @@ def _execute_view_files_with_symbol(coder, symbol):
71
77
  # Use absolute path directly if available, otherwise resolve from relative path
72
78
  abs_fname = rel_fname_to_abs.get(tag.rel_fname) or coder.abs_root_path(tag.fname)
73
79
  if abs_fname in files_to_search: # Ensure we only add files we intended to search
74
- found_files.add(abs_fname)
80
+ found_files.add(coder.get_rel_fname(abs_fname))
75
81
 
76
- # Limit the number of files added
77
- if len(found_files) > coder.max_files_per_glob:
78
- coder.io.tool_output(
79
- f"⚠️ Found symbol '{symbol}' in {len(found_files)} files, "
80
- f"limiting to {coder.max_files_per_glob} most relevant files."
81
- )
82
- # Sort by modification time (most recent first) - approximate relevance
83
- sorted_found_files = sorted(
84
- list(found_files), key=lambda f: os.path.getmtime(f), reverse=True
85
- )
86
- found_files = set(sorted_found_files[: coder.max_files_per_glob])
87
-
88
- # Add files to context (as read-only)
89
- added_count = 0
90
- added_files_rel = []
91
- for abs_file_path in found_files:
92
- rel_path = coder.get_rel_fname(abs_file_path)
93
- # Double check it's not already added somehow
94
- if (
95
- abs_file_path not in coder.abs_fnames
96
- and abs_file_path not in coder.abs_read_only_fnames
97
- ):
98
- # Use explicit=True for clear output, even though it's an external search result
99
- add_result = coder._add_file_to_context(rel_path, explicit=True)
100
- if "Added" in add_result or "Viewed" in add_result: # Count successful adds/views
101
- added_count += 1
102
- added_files_rel.append(rel_path)
103
-
104
- if added_count > 0:
105
- if added_count > 5:
106
- brief = ", ".join(added_files_rel[:5]) + f", and {added_count - 5} more"
107
- coder.io.tool_output(f"🔎 Found '{symbol}' and added {added_count} files: {brief}")
82
+ # Return formatted text instead of adding to context
83
+ if found_files:
84
+ found_files_list = sorted(list(found_files))
85
+ if len(found_files) > 10:
86
+ result = (
87
+ f"Found symbol '{symbol}' in {len(found_files)} files:"
88
+ f" {', '.join(found_files_list[:10])} and {len(found_files) - 10} more"
89
+ )
90
+ coder.io.tool_output(f"🔎 Found '{symbol}' in {len(found_files)} files")
108
91
  else:
92
+ result = (
93
+ f"Found symbol '{symbol}' in {len(found_files)} files:"
94
+ f" {', '.join(found_files_list)}"
95
+ )
109
96
  coder.io.tool_output(
110
- f"🔎 Found '{symbol}' and added files: {', '.join(added_files_rel)}"
97
+ f"🔎 Found '{symbol}' in files:"
98
+ f" {', '.join(found_files_list[:5])}{' and more' if len(found_files) > 5 else ''}"
111
99
  )
112
- return f"Found symbol '{symbol}' and added {added_count} files as read-only."
100
+
101
+ return result
113
102
  else:
114
- coder.io.tool_output(
115
- f"⚠️ Symbol '{symbol}' not found in searchable files (outside current context)."
116
- )
117
- return f"Symbol '{symbol}' not found in searchable files (outside current context)."
103
+ coder.io.tool_output(f"⚠️ Symbol '{symbol}' not found in searchable files")
104
+ return f"Symbol '{symbol}' not found in searchable files"
118
105
 
119
106
  except Exception as e:
120
107
  coder.io.tool_error(f"Error in ViewFilesWithSymbol: {str(e)}")
@@ -0,0 +1,57 @@
1
+ from .tool_utils import ToolError, format_tool_result, handle_tool_error
2
+
3
+ view_todo_list_schema = {
4
+ "type": "function",
5
+ "function": {
6
+ "name": "ViewTodoList",
7
+ "description": "View the current todo list for tracking conversation steps and progress.",
8
+ "parameters": {
9
+ "type": "object",
10
+ "properties": {},
11
+ "required": [],
12
+ },
13
+ },
14
+ }
15
+
16
+
17
+ def _execute_view_todo_list(coder):
18
+ """
19
+ View the current todo list from .aider.todo.txt file.
20
+ Returns the todo list content or creates an empty one if it doesn't exist.
21
+ """
22
+ tool_name = "ViewTodoList"
23
+ try:
24
+ # Define the todo file path
25
+ todo_file_path = ".aider.todo.txt"
26
+ abs_path = coder.abs_root_path(todo_file_path)
27
+
28
+ # Check if file exists
29
+ import os
30
+
31
+ if os.path.isfile(abs_path):
32
+ # Read existing todo list
33
+ content = coder.io.read_text(abs_path)
34
+ if content is None:
35
+ raise ToolError(f"Could not read todo list file: {todo_file_path}")
36
+
37
+ # Check if content exceeds 4096 characters and warn
38
+ if len(content) > 4096:
39
+ coder.io.tool_warning(
40
+ "⚠️ Todo list content exceeds 4096 characters. Consider summarizing the plan"
41
+ " before proceeding."
42
+ )
43
+
44
+ if content.strip():
45
+ result_message = f"Current todo list:\n```\n{content}\n```"
46
+ else:
47
+ result_message = "Todo list is empty. Use UpdateTodoList to add items."
48
+ else:
49
+ # Create empty todo list
50
+ result_message = "Todo list is empty. Use UpdateTodoList to add items."
51
+
52
+ return format_tool_result(coder, tool_name, result_message)
53
+
54
+ except ToolError as e:
55
+ return handle_tool_error(coder, tool_name, e, add_traceback=False)
56
+ except Exception as e:
57
+ return handle_tool_error(coder, tool_name, e)