aider-ce 0.88.20__py3-none-any.whl → 0.88.38__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.
Files changed (113) hide show
  1. aider/__init__.py +1 -1
  2. aider/_version.py +2 -2
  3. aider/args.py +63 -43
  4. aider/coders/agent_coder.py +331 -79
  5. aider/coders/agent_prompts.py +3 -15
  6. aider/coders/architect_coder.py +21 -5
  7. aider/coders/base_coder.py +661 -413
  8. aider/coders/base_prompts.py +6 -3
  9. aider/coders/chat_chunks.py +39 -17
  10. aider/commands.py +79 -15
  11. aider/diffs.py +10 -9
  12. aider/exceptions.py +1 -1
  13. aider/helpers/coroutines.py +8 -0
  14. aider/helpers/requests.py +45 -0
  15. aider/history.py +5 -0
  16. aider/io.py +179 -25
  17. aider/main.py +86 -35
  18. aider/models.py +16 -8
  19. aider/queries/tree-sitter-language-pack/c-tags.scm +3 -0
  20. aider/queries/tree-sitter-language-pack/clojure-tags.scm +5 -0
  21. aider/queries/tree-sitter-language-pack/commonlisp-tags.scm +5 -0
  22. aider/queries/tree-sitter-language-pack/cpp-tags.scm +3 -0
  23. aider/queries/tree-sitter-language-pack/csharp-tags.scm +6 -0
  24. aider/queries/tree-sitter-language-pack/dart-tags.scm +5 -0
  25. aider/queries/tree-sitter-language-pack/elixir-tags.scm +5 -0
  26. aider/queries/tree-sitter-language-pack/elm-tags.scm +3 -0
  27. aider/queries/tree-sitter-language-pack/go-tags.scm +7 -0
  28. aider/queries/tree-sitter-language-pack/java-tags.scm +6 -0
  29. aider/queries/tree-sitter-language-pack/javascript-tags.scm +8 -0
  30. aider/queries/tree-sitter-language-pack/lua-tags.scm +5 -0
  31. aider/queries/tree-sitter-language-pack/ocaml_interface-tags.scm +3 -0
  32. aider/queries/tree-sitter-language-pack/python-tags.scm +10 -0
  33. aider/queries/tree-sitter-language-pack/r-tags.scm +6 -0
  34. aider/queries/tree-sitter-language-pack/ruby-tags.scm +5 -0
  35. aider/queries/tree-sitter-language-pack/rust-tags.scm +3 -0
  36. aider/queries/tree-sitter-language-pack/solidity-tags.scm +1 -1
  37. aider/queries/tree-sitter-language-pack/swift-tags.scm +4 -1
  38. aider/queries/tree-sitter-languages/c-tags.scm +3 -0
  39. aider/queries/tree-sitter-languages/c_sharp-tags.scm +6 -0
  40. aider/queries/tree-sitter-languages/cpp-tags.scm +3 -0
  41. aider/queries/tree-sitter-languages/dart-tags.scm +2 -1
  42. aider/queries/tree-sitter-languages/elixir-tags.scm +5 -0
  43. aider/queries/tree-sitter-languages/elm-tags.scm +3 -0
  44. aider/queries/tree-sitter-languages/fortran-tags.scm +3 -0
  45. aider/queries/tree-sitter-languages/go-tags.scm +6 -0
  46. aider/queries/tree-sitter-languages/haskell-tags.scm +2 -0
  47. aider/queries/tree-sitter-languages/java-tags.scm +6 -0
  48. aider/queries/tree-sitter-languages/javascript-tags.scm +8 -0
  49. aider/queries/tree-sitter-languages/julia-tags.scm +2 -2
  50. aider/queries/tree-sitter-languages/kotlin-tags.scm +3 -0
  51. aider/queries/tree-sitter-languages/ocaml_interface-tags.scm +6 -0
  52. aider/queries/tree-sitter-languages/php-tags.scm +6 -0
  53. aider/queries/tree-sitter-languages/python-tags.scm +10 -0
  54. aider/queries/tree-sitter-languages/ruby-tags.scm +5 -0
  55. aider/queries/tree-sitter-languages/rust-tags.scm +3 -0
  56. aider/queries/tree-sitter-languages/scala-tags.scm +2 -3
  57. aider/queries/tree-sitter-languages/typescript-tags.scm +3 -0
  58. aider/queries/tree-sitter-languages/zig-tags.scm +20 -3
  59. aider/repomap.py +71 -11
  60. aider/resources/model-metadata.json +27335 -635
  61. aider/resources/model-settings.yml +190 -0
  62. aider/scrape.py +2 -0
  63. aider/tools/__init__.py +2 -0
  64. aider/tools/command.py +84 -94
  65. aider/tools/command_interactive.py +95 -110
  66. aider/tools/delete_block.py +131 -159
  67. aider/tools/delete_line.py +97 -132
  68. aider/tools/delete_lines.py +120 -160
  69. aider/tools/extract_lines.py +288 -312
  70. aider/tools/finished.py +30 -43
  71. aider/tools/git_branch.py +107 -109
  72. aider/tools/git_diff.py +44 -56
  73. aider/tools/git_log.py +39 -53
  74. aider/tools/git_remote.py +37 -51
  75. aider/tools/git_show.py +33 -47
  76. aider/tools/git_status.py +30 -44
  77. aider/tools/grep.py +214 -242
  78. aider/tools/indent_lines.py +175 -201
  79. aider/tools/insert_block.py +220 -253
  80. aider/tools/list_changes.py +65 -80
  81. aider/tools/ls.py +64 -80
  82. aider/tools/make_editable.py +57 -73
  83. aider/tools/make_readonly.py +50 -66
  84. aider/tools/remove.py +64 -80
  85. aider/tools/replace_all.py +96 -109
  86. aider/tools/replace_line.py +118 -156
  87. aider/tools/replace_lines.py +160 -197
  88. aider/tools/replace_text.py +159 -160
  89. aider/tools/show_numbered_context.py +115 -141
  90. aider/tools/thinking.py +52 -0
  91. aider/tools/undo_change.py +78 -91
  92. aider/tools/update_todo_list.py +130 -138
  93. aider/tools/utils/base_tool.py +64 -0
  94. aider/tools/utils/output.py +118 -0
  95. aider/tools/view.py +38 -54
  96. aider/tools/view_files_matching.py +131 -134
  97. aider/tools/view_files_with_symbol.py +108 -120
  98. aider/urls.py +1 -1
  99. aider/versioncheck.py +4 -3
  100. aider/website/docs/config/adv-model-settings.md +237 -0
  101. aider/website/docs/config/agent-mode.md +36 -3
  102. aider/website/docs/config/model-aliases.md +2 -1
  103. aider/website/docs/faq.md +6 -11
  104. aider/website/docs/languages.md +2 -2
  105. aider/website/docs/more/infinite-output.md +27 -0
  106. {aider_ce-0.88.20.dist-info → aider_ce-0.88.38.dist-info}/METADATA +112 -70
  107. {aider_ce-0.88.20.dist-info → aider_ce-0.88.38.dist-info}/RECORD +112 -107
  108. aider_ce-0.88.38.dist-info/entry_points.txt +6 -0
  109. aider_ce-0.88.20.dist-info/entry_points.txt +0 -2
  110. /aider/tools/{tool_utils.py → utils/helpers.py} +0 -0
  111. {aider_ce-0.88.20.dist-info → aider_ce-0.88.38.dist-info}/WHEEL +0 -0
  112. {aider_ce-0.88.20.dist-info → aider_ce-0.88.38.dist-info}/licenses/LICENSE.txt +0 -0
  113. {aider_ce-0.88.20.dist-info → aider_ce-0.88.38.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,52 @@
1
+ import json
2
+
3
+ from aider.tools.utils.base_tool import BaseTool
4
+ from aider.tools.utils.output import color_markers, tool_footer, tool_header
5
+
6
+
7
+ class Tool(BaseTool):
8
+ NORM_NAME = "thinking"
9
+ SCHEMA = {
10
+ "type": "function",
11
+ "function": {
12
+ "name": "Thinking",
13
+ "description": (
14
+ "Use this tool to store useful facts for later "
15
+ "keep a scratch pad of your current efforts "
16
+ "and clarify your thoughts and intentions for your next steps."
17
+ ),
18
+ "parameters": {
19
+ "type": "object",
20
+ "properties": {
21
+ "content": {
22
+ "type": "string",
23
+ "description": "Textual information to record in the context",
24
+ },
25
+ },
26
+ "required": ["content"],
27
+ },
28
+ },
29
+ }
30
+
31
+ @classmethod
32
+ def execute(cls, coder, content):
33
+ """
34
+ A place to allow the model to record freeform text as it
35
+ iterates over tools to ideally help it guide itself to a proper solution
36
+ """
37
+ coder.io.tool_output("🧠 Thoughts recorded in context")
38
+ return content
39
+
40
+ @classmethod
41
+ def format_output(cls, coder, mcp_server, tool_response):
42
+ color_start, color_end = color_markers(coder)
43
+ params = json.loads(tool_response.function.arguments)
44
+
45
+ tool_header(coder=coder, mcp_server=mcp_server, tool_response=tool_response)
46
+
47
+ coder.io.tool_output("")
48
+ coder.io.tool_output(f"{color_start}Thoughts:{color_end}")
49
+ coder.io.tool_output(params["content"])
50
+ coder.io.tool_output("")
51
+
52
+ tool_footer(coder=coder, tool_response=tool_response)
@@ -1,95 +1,82 @@
1
1
  import traceback
2
2
 
3
- 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"},
3
+ from aider.tools.utils.base_tool import BaseTool
4
+
5
+
6
+ class Tool(BaseTool):
7
+ NORM_NAME = "undochange"
8
+ SCHEMA = {
9
+ "type": "function",
10
+ "function": {
11
+ "name": "UndoChange",
12
+ "description": "Undo a previously applied change.",
13
+ "parameters": {
14
+ "type": "object",
15
+ "properties": {
16
+ "change_id": {"type": "string"},
17
+ "file_path": {"type": "string"},
18
+ },
13
19
  },
14
20
  },
15
- },
16
- }
17
-
18
- # Normalized tool name for lookup
19
- NORM_NAME = "undochange"
20
-
21
-
22
- def _execute_undo_change(coder, change_id=None, file_path=None):
23
- """
24
- Undo a specific change by ID, or the last change to a file.
25
-
26
- Parameters:
27
- - coder: The Coder instance
28
- - change_id: ID of the change to undo
29
- - file_path: Path to file where the last change should be undone
30
-
31
- Returns a result message.
32
- """
33
- # Note: Undo does not have a dry_run parameter as it's inherently about reverting a previous action.
34
- try:
35
- # Validate parameters
36
- if change_id is None and file_path is None:
37
- coder.io.tool_error("Must specify either change_id or file_path for UndoChange")
38
- return "Error: Must specify either change_id or file_path"
39
-
40
- # If file_path is specified, get the most recent change for that file
41
- if file_path:
42
- abs_path = coder.abs_root_path(file_path)
43
- rel_path = coder.get_rel_fname(abs_path)
44
-
45
- change_id = coder.change_tracker.get_last_change(rel_path)
46
- if not change_id:
47
- coder.io.tool_error(f"No tracked changes found for file '{file_path}' to undo.")
48
- return f"Error: No changes found for file '{file_path}'"
49
-
50
- # Attempt to get undo information from the tracker
51
- success, message, change_info = coder.change_tracker.undo_change(change_id)
52
-
53
- if not success:
54
- coder.io.tool_error(f"Failed to undo change '{change_id}': {message}")
55
- return f"Error: {message}"
56
-
57
- # Apply the undo by restoring the original content
58
- if change_info:
59
- file_path = change_info["file_path"]
60
- abs_path = coder.abs_root_path(file_path)
61
- # Write the original content back to the file
62
- coder.io.write_text(abs_path, change_info["original"])
63
- coder.aider_edited_files.add(file_path) # Track that the file was modified by the undo
64
-
65
- change_type = change_info["type"]
66
- coder.io.tool_output(f"✅ Undid {change_type} change '{change_id}' in {file_path}")
67
- return f"Successfully undid {change_type} change '{change_id}'."
68
- else:
69
- # This case should ideally not be reached if tracker returns success
70
- coder.io.tool_error(
71
- f"Failed to undo change '{change_id}': Change info missing after successful tracker"
72
- " update."
73
- )
74
- return f"Error: Failed to undo change '{change_id}' (missing change info)"
75
-
76
- except Exception as e:
77
- coder.io.tool_error(f"Error in UndoChange: {str(e)}\n{traceback.format_exc()}")
78
- return f"Error: {str(e)}"
79
-
80
-
81
- def process_response(coder, params):
82
- """
83
- Process the UndoChange tool response.
84
-
85
- Args:
86
- coder: The Coder instance
87
- params: Dictionary of parameters
88
-
89
- Returns:
90
- str: Result message
91
- """
92
- change_id = params.get("change_id")
93
- file_path = params.get("file_path")
94
-
95
- return _execute_undo_change(coder, change_id, file_path)
21
+ }
22
+
23
+ @classmethod
24
+ def execute(cls, coder, change_id=None, file_path=None):
25
+ """
26
+ Undo a specific change by ID, or the last change to a file.
27
+
28
+ Parameters:
29
+ - coder: The Coder instance
30
+ - change_id: ID of the change to undo
31
+ - file_path: Path to file where the last change should be undone
32
+
33
+ Returns a result message.
34
+ """
35
+ # Note: Undo does not have a dry_run parameter as it's inherently about reverting a previous action.
36
+ try:
37
+ # Validate parameters
38
+ if change_id is None and file_path is None:
39
+ coder.io.tool_error("Must specify either change_id or file_path for UndoChange")
40
+ return "Error: Must specify either change_id or file_path"
41
+
42
+ # If file_path is specified, get the most recent change for that file
43
+ if file_path:
44
+ abs_path = coder.abs_root_path(file_path)
45
+ rel_path = coder.get_rel_fname(abs_path)
46
+
47
+ change_id = coder.change_tracker.get_last_change(rel_path)
48
+ if not change_id:
49
+ coder.io.tool_error(f"No tracked changes found for file '{file_path}' to undo.")
50
+ return f"Error: No changes found for file '{file_path}'"
51
+
52
+ # Attempt to get undo information from the tracker
53
+ success, message, change_info = coder.change_tracker.undo_change(change_id)
54
+
55
+ if not success:
56
+ coder.io.tool_error(f"Failed to undo change '{change_id}': {message}")
57
+ return f"Error: {message}"
58
+
59
+ # Apply the undo by restoring the original content
60
+ if change_info:
61
+ file_path = change_info["file_path"]
62
+ abs_path = coder.abs_root_path(file_path)
63
+ # Write the original content back to the file
64
+ coder.io.write_text(abs_path, change_info["original"])
65
+ coder.aider_edited_files.add(
66
+ file_path
67
+ ) # Track that the file was modified by the undo
68
+
69
+ change_type = change_info["type"]
70
+ coder.io.tool_output(f"✅ Undid {change_type} change '{change_id}' in {file_path}")
71
+ return f"Successfully undid {change_type} change '{change_id}'."
72
+ else:
73
+ # This case should ideally not be reached if tracker returns success
74
+ coder.io.tool_error(
75
+ f"Failed to undo change '{change_id}': Change info missing after successful"
76
+ " tracker update."
77
+ )
78
+ return f"Error: Failed to undo change '{change_id}' (missing change info)"
79
+
80
+ except Exception as e:
81
+ coder.io.tool_error(f"Error in UndoChange: {str(e)}\n{traceback.format_exc()}")
82
+ return f"Error: {str(e)}"
@@ -1,156 +1,148 @@
1
- from .tool_utils import (
1
+ from aider.tools.utils.base_tool import BaseTool
2
+ from aider.tools.utils.helpers import (
2
3
  ToolError,
3
4
  format_tool_result,
4
5
  generate_unified_diff_snippet,
5
6
  handle_tool_error,
6
7
  )
7
-
8
- 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
- ),
8
+ from aider.tools.utils.output import tool_body_unwrapped, tool_footer, tool_header
9
+
10
+
11
+ class Tool(BaseTool):
12
+ NORM_NAME = "updatetodolist"
13
+ SCHEMA = {
14
+ "type": "function",
15
+ "function": {
16
+ "name": "UpdateTodoList",
17
+ "description": "Update the todo list with new items or modify existing ones.",
18
+ "parameters": {
19
+ "type": "object",
20
+ "properties": {
21
+ "content": {
22
+ "type": "string",
23
+ "description": "The new content for the todo list.",
24
+ },
25
+ "append": {
26
+ "type": "boolean",
27
+ "description": (
28
+ "Whether to append to existing content instead of replacing it."
29
+ " Defaults to False."
30
+ ),
31
+ },
32
+ "change_id": {
33
+ "type": "string",
34
+ "description": "Optional change ID for tracking.",
35
+ },
36
+ "dry_run": {
37
+ "type": "boolean",
38
+ "description": (
39
+ "Whether to perform a dry run without actually updating the file."
40
+ " Defaults to False."
41
+ ),
42
+ },
37
43
  },
44
+ "required": ["content"],
38
45
  },
39
- "required": ["content"],
40
46
  },
41
- },
42
- }
43
-
44
- # Normalized tool name for lookup
45
- NORM_NAME = "updatetodolist"
46
-
47
-
48
- def _execute_update_todo_list(coder, content, append=False, change_id=None, dry_run=False):
49
- """
50
- Update the todo list file (.aider.todo.txt) with new content.
51
- Can either replace the entire content or append to it.
52
- """
53
- tool_name = "UpdateTodoList"
54
- try:
55
- # Define the todo file path
56
- todo_file_path = ".aider.todo.txt"
57
- abs_path = coder.abs_root_path(todo_file_path)
58
-
59
- # Get existing content if appending
60
- existing_content = ""
61
- import os
62
-
63
- if os.path.isfile(abs_path):
64
- existing_content = coder.io.read_text(abs_path) or ""
65
-
66
- # Prepare new content
67
- if append:
68
- if existing_content and not existing_content.endswith("\n"):
69
- existing_content += "\n"
70
- new_content = existing_content + content
71
- else:
72
- new_content = content
73
-
74
- # Check if content exceeds 4096 characters and warn
75
- if len(new_content) > 4096:
76
- coder.io.tool_warning(
77
- "⚠️ Todo list content exceeds 4096 characters. Consider summarizing the plan before"
78
- " proceeding."
47
+ }
48
+
49
+ @classmethod
50
+ def execute(cls, coder, content, append=False, change_id=None, dry_run=False):
51
+ """
52
+ Update the todo list file (.aider.todo.txt) with new content.
53
+ Can either replace the entire content or append to it.
54
+ """
55
+ tool_name = "UpdateTodoList"
56
+ try:
57
+ # Define the todo file path
58
+ todo_file_path = ".aider.todo.txt"
59
+ abs_path = coder.abs_root_path(todo_file_path)
60
+
61
+ # Get existing content if appending
62
+ existing_content = ""
63
+ import os
64
+
65
+ if os.path.isfile(abs_path):
66
+ existing_content = coder.io.read_text(abs_path) or ""
67
+
68
+ # Prepare new content
69
+ if append:
70
+ if existing_content and not existing_content.endswith("\n"):
71
+ existing_content += "\n"
72
+ new_content = existing_content + content
73
+ else:
74
+ new_content = content
75
+
76
+ # Check if content exceeds 4096 characters and warn
77
+ if len(new_content) > 4096:
78
+ coder.io.tool_warning(
79
+ "⚠️ Todo list content exceeds 4096 characters. Consider summarizing the plan"
80
+ " before proceeding."
81
+ )
82
+
83
+ # Check if content actually changed
84
+ if existing_content == new_content:
85
+ coder.io.tool_warning("No changes made: new content is identical to existing")
86
+ return "Warning: No changes made (content identical to existing)"
87
+
88
+ # Generate diff for feedback
89
+ diff_snippet = generate_unified_diff_snippet(
90
+ existing_content, new_content, todo_file_path
79
91
  )
80
92
 
81
- # Check if content actually changed
82
- if existing_content == new_content:
83
- coder.io.tool_warning("No changes made: new content is identical to existing")
84
- return "Warning: No changes made (content identical to existing)"
93
+ # Handle dry run
94
+ if dry_run:
95
+ action = "append to" if append else "replace"
96
+ dry_run_message = f"Dry run: Would {action} todo list in {todo_file_path}."
97
+ return format_tool_result(
98
+ coder,
99
+ tool_name,
100
+ "",
101
+ dry_run=True,
102
+ dry_run_message=dry_run_message,
103
+ diff_snippet=diff_snippet,
104
+ )
105
+
106
+ # Apply change
107
+ metadata = {
108
+ "append": append,
109
+ "existing_length": len(existing_content),
110
+ "new_length": len(new_content),
111
+ }
112
+
113
+ # Write the file directly since it's a special file
114
+ coder.io.write_text(abs_path, new_content)
115
+
116
+ # Track the change
117
+ final_change_id = coder.change_tracker.track_change(
118
+ file_path=todo_file_path,
119
+ change_type="updatetodolist",
120
+ original_content=existing_content,
121
+ new_content=new_content,
122
+ metadata=metadata,
123
+ change_id=change_id,
124
+ )
85
125
 
86
- # Generate diff for feedback
87
- diff_snippet = generate_unified_diff_snippet(existing_content, new_content, todo_file_path)
126
+ coder.aider_edited_files.add(todo_file_path)
88
127
 
89
- # Handle dry run
90
- if dry_run:
91
- action = "append to" if append else "replace"
92
- dry_run_message = f"Dry run: Would {action} todo list in {todo_file_path}."
128
+ # Format and return result
129
+ action = "appended to" if append else "updated"
130
+ success_message = f"Successfully {action} todo list in {todo_file_path}"
93
131
  return format_tool_result(
94
132
  coder,
95
133
  tool_name,
96
- "",
97
- dry_run=True,
98
- dry_run_message=dry_run_message,
134
+ success_message,
135
+ change_id=final_change_id,
99
136
  diff_snippet=diff_snippet,
100
137
  )
101
138
 
102
- # Apply change
103
- metadata = {
104
- "append": append,
105
- "existing_length": len(existing_content),
106
- "new_length": len(new_content),
107
- }
108
-
109
- # Write the file directly since it's a special file
110
- coder.io.write_text(abs_path, new_content)
111
-
112
- # Track the change
113
- final_change_id = coder.change_tracker.track_change(
114
- file_path=todo_file_path,
115
- change_type="updatetodolist",
116
- original_content=existing_content,
117
- new_content=new_content,
118
- metadata=metadata,
119
- change_id=change_id,
120
- )
121
-
122
- coder.aider_edited_files.add(todo_file_path)
123
-
124
- # Format and return result
125
- action = "appended to" if append else "updated"
126
- success_message = f"Successfully {action} todo list in {todo_file_path}"
127
- return format_tool_result(
128
- coder, tool_name, success_message, change_id=final_change_id, diff_snippet=diff_snippet
129
- )
130
-
131
- except ToolError as e:
132
- return handle_tool_error(coder, tool_name, e, add_traceback=False)
133
- except Exception as e:
134
- return handle_tool_error(coder, tool_name, e)
135
-
136
-
137
- def process_response(coder, params):
138
- """
139
- Process the UpdateTodoList tool response.
140
-
141
- Args:
142
- coder: The Coder instance
143
- params: Dictionary of parameters
144
-
145
- Returns:
146
- str: Result message
147
- """
148
- content = params.get("content")
149
- append = params.get("append", False)
150
- change_id = params.get("change_id")
151
- dry_run = params.get("dry_run", False)
152
-
153
- if content is not None:
154
- return _execute_update_todo_list(coder, content, append, change_id, dry_run)
155
- else:
156
- return "Error: Missing required 'content' parameter for UpdateTodoList"
139
+ except ToolError as e:
140
+ return handle_tool_error(coder, tool_name, e, add_traceback=False)
141
+ except Exception as e:
142
+ return handle_tool_error(coder, tool_name, e)
143
+
144
+ @classmethod
145
+ def format_output(cls, coder, mcp_server, tool_response):
146
+ tool_header(coder=coder, mcp_server=mcp_server, tool_response=tool_response)
147
+ tool_body_unwrapped(coder=coder, tool_response=tool_response)
148
+ tool_footer(coder=coder, tool_response=tool_response)
@@ -0,0 +1,64 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+ from aider.tools.utils.helpers import handle_tool_error
4
+ from aider.tools.utils.output import print_tool_response
5
+
6
+
7
+ class BaseTool(ABC):
8
+ """Abstract base class for all tools."""
9
+
10
+ # Class properties that must be defined by subclasses
11
+ # Note: NORM_NAME should be the lowercase version of the function name in the SCHEMA
12
+ NORM_NAME = None
13
+ SCHEMA = None
14
+
15
+ @classmethod
16
+ @abstractmethod
17
+ def execute(cls, coder, **params):
18
+ """
19
+ Execute the tool with the given parameters.
20
+
21
+ Args:
22
+ coder: The Coder instance
23
+ **params: Tool-specific parameters
24
+
25
+ Returns:
26
+ str: Result message
27
+ """
28
+ pass
29
+
30
+ @classmethod
31
+ def process_response(cls, coder, params):
32
+ """
33
+ Process the tool response by creating an instance and calling execute.
34
+
35
+ Args:
36
+ coder: The Coder instance
37
+ params: Dictionary of parameters
38
+
39
+ Returns:
40
+ str: Result message
41
+ """
42
+
43
+ # Validate required parameters from SCHEMA
44
+ if cls.SCHEMA and "function" in cls.SCHEMA:
45
+ function_schema = cls.SCHEMA["function"]
46
+
47
+ if "parameters" in function_schema and "required" in function_schema["parameters"]:
48
+ required_params = function_schema["parameters"]["required"]
49
+ missing_params = [param for param in required_params if param not in params]
50
+ if missing_params:
51
+ tool_name = function_schema.get("name", "Unknown Tool")
52
+ error_msg = (
53
+ f"Missing required parameters for {tool_name}: {', '.join(missing_params)}"
54
+ )
55
+ return handle_tool_error(coder, tool_name, ValueError(error_msg))
56
+
57
+ try:
58
+ return cls.execute(coder, **params)
59
+ except Exception as e:
60
+ return handle_tool_error(coder, cls.SCHEMA.get("function").get("name"), e)
61
+
62
+ @classmethod
63
+ def format_output(cls, coder, mcp_server, tool_response):
64
+ print_tool_response(coder=coder, mcp_server=mcp_server, tool_response=tool_response)