aider-ce 0.87.13__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.
- aider/__init__.py +1 -1
- aider/_version.py +2 -2
- aider/args.py +6 -0
- aider/coders/architect_coder.py +3 -3
- aider/coders/base_coder.py +505 -184
- aider/coders/context_coder.py +1 -1
- aider/coders/editblock_func_coder.py +2 -2
- aider/coders/navigator_coder.py +451 -649
- aider/coders/navigator_legacy_prompts.py +49 -284
- aider/coders/navigator_prompts.py +46 -473
- aider/coders/search_replace.py +0 -0
- aider/coders/wholefile_func_coder.py +2 -2
- aider/commands.py +56 -44
- aider/history.py +14 -12
- aider/io.py +354 -117
- aider/llm.py +12 -4
- aider/main.py +22 -19
- aider/mcp/__init__.py +65 -2
- aider/mcp/server.py +37 -11
- aider/models.py +45 -20
- aider/onboarding.py +4 -4
- aider/repo.py +7 -7
- aider/resources/model-metadata.json +8 -8
- aider/scrape.py +2 -2
- aider/sendchat.py +185 -15
- aider/tools/__init__.py +44 -23
- aider/tools/command.py +18 -0
- aider/tools/command_interactive.py +18 -0
- aider/tools/delete_block.py +23 -0
- aider/tools/delete_line.py +19 -1
- aider/tools/delete_lines.py +20 -1
- aider/tools/extract_lines.py +25 -2
- aider/tools/git.py +142 -0
- aider/tools/grep.py +47 -2
- aider/tools/indent_lines.py +25 -0
- aider/tools/insert_block.py +26 -0
- aider/tools/list_changes.py +15 -0
- aider/tools/ls.py +24 -1
- aider/tools/make_editable.py +18 -0
- aider/tools/make_readonly.py +19 -0
- aider/tools/remove.py +22 -0
- aider/tools/replace_all.py +21 -0
- aider/tools/replace_line.py +20 -1
- aider/tools/replace_lines.py +21 -1
- aider/tools/replace_text.py +22 -0
- aider/tools/show_numbered_context.py +18 -0
- aider/tools/undo_change.py +15 -0
- aider/tools/update_todo_list.py +131 -0
- aider/tools/view.py +23 -0
- aider/tools/view_files_at_glob.py +32 -27
- aider/tools/view_files_matching.py +51 -37
- aider/tools/view_files_with_symbol.py +41 -54
- aider/tools/view_todo_list.py +57 -0
- aider/waiting.py +20 -203
- {aider_ce-0.87.13.dist-info → aider_ce-0.88.0.dist-info}/METADATA +21 -5
- {aider_ce-0.87.13.dist-info → aider_ce-0.88.0.dist-info}/RECORD +59 -56
- {aider_ce-0.87.13.dist-info → aider_ce-0.88.0.dist-info}/WHEEL +0 -0
- {aider_ce-0.87.13.dist-info → aider_ce-0.88.0.dist-info}/entry_points.txt +0 -0
- {aider_ce-0.87.13.dist-info → aider_ce-0.88.0.dist-info}/licenses/LICENSE.txt +0 -0
- {aider_ce-0.87.13.dist-info → aider_ce-0.88.0.dist-info}/top_level.txt +0 -0
aider/coders/navigator_coder.py
CHANGED
|
@@ -10,7 +10,7 @@ import time
|
|
|
10
10
|
import traceback
|
|
11
11
|
|
|
12
12
|
# Add necessary imports if not already present
|
|
13
|
-
from collections import defaultdict
|
|
13
|
+
from collections import Counter, defaultdict
|
|
14
14
|
from datetime import datetime
|
|
15
15
|
from pathlib import Path
|
|
16
16
|
|
|
@@ -24,12 +24,48 @@ from aider.mcp.server import LocalServer
|
|
|
24
24
|
from aider.repo import ANY_GIT_ERROR
|
|
25
25
|
|
|
26
26
|
# Import run_cmd for potentially interactive execution and run_cmd_subprocess for guaranteed non-interactive
|
|
27
|
+
from aider.tools import (
|
|
28
|
+
command_interactive_schema,
|
|
29
|
+
command_schema,
|
|
30
|
+
delete_block_schema,
|
|
31
|
+
delete_line_schema,
|
|
32
|
+
delete_lines_schema,
|
|
33
|
+
extract_lines_schema,
|
|
34
|
+
grep_schema,
|
|
35
|
+
indent_lines_schema,
|
|
36
|
+
insert_block_schema,
|
|
37
|
+
list_changes_schema,
|
|
38
|
+
ls_schema,
|
|
39
|
+
make_editable_schema,
|
|
40
|
+
make_readonly_schema,
|
|
41
|
+
remove_schema,
|
|
42
|
+
replace_all_schema,
|
|
43
|
+
replace_line_schema,
|
|
44
|
+
replace_lines_schema,
|
|
45
|
+
replace_text_schema,
|
|
46
|
+
show_numbered_context_schema,
|
|
47
|
+
undo_change_schema,
|
|
48
|
+
update_todo_list_schema,
|
|
49
|
+
view_files_matching_schema,
|
|
50
|
+
view_files_with_symbol_schema,
|
|
51
|
+
view_schema,
|
|
52
|
+
)
|
|
27
53
|
from aider.tools.command import _execute_command
|
|
28
54
|
from aider.tools.command_interactive import _execute_command_interactive
|
|
29
55
|
from aider.tools.delete_block import _execute_delete_block
|
|
30
56
|
from aider.tools.delete_line import _execute_delete_line
|
|
31
57
|
from aider.tools.delete_lines import _execute_delete_lines
|
|
32
58
|
from aider.tools.extract_lines import _execute_extract_lines
|
|
59
|
+
from aider.tools.git import (
|
|
60
|
+
_execute_git_diff,
|
|
61
|
+
_execute_git_log,
|
|
62
|
+
_execute_git_show,
|
|
63
|
+
_execute_git_status,
|
|
64
|
+
git_diff_schema,
|
|
65
|
+
git_log_schema,
|
|
66
|
+
git_show_schema,
|
|
67
|
+
git_status_schema,
|
|
68
|
+
)
|
|
33
69
|
from aider.tools.grep import _execute_grep
|
|
34
70
|
from aider.tools.indent_lines import _execute_indent_lines
|
|
35
71
|
from aider.tools.insert_block import _execute_insert_block
|
|
@@ -44,10 +80,10 @@ from aider.tools.replace_lines import _execute_replace_lines
|
|
|
44
80
|
from aider.tools.replace_text import _execute_replace_text
|
|
45
81
|
from aider.tools.show_numbered_context import execute_show_numbered_context
|
|
46
82
|
from aider.tools.undo_change import _execute_undo_change
|
|
83
|
+
from aider.tools.update_todo_list import _execute_update_todo_list
|
|
47
84
|
from aider.tools.view import execute_view
|
|
48
85
|
|
|
49
86
|
# Import tool functions
|
|
50
|
-
from aider.tools.view_files_at_glob import execute_view_files_at_glob
|
|
51
87
|
from aider.tools.view_files_matching import execute_view_files_matching
|
|
52
88
|
from aider.tools.view_files_with_symbol import _execute_view_files_with_symbol
|
|
53
89
|
|
|
@@ -56,6 +92,22 @@ from .editblock_coder import do_replace, find_original_update_blocks, find_simil
|
|
|
56
92
|
from .navigator_legacy_prompts import NavigatorLegacyPrompts
|
|
57
93
|
from .navigator_prompts import NavigatorPrompts
|
|
58
94
|
|
|
95
|
+
# UNUSED TOOL SCHEMAS
|
|
96
|
+
# view_files_matching_schema,
|
|
97
|
+
# grep_schema,
|
|
98
|
+
# replace_all_schema,
|
|
99
|
+
# insert_block_schema,
|
|
100
|
+
# delete_block_schema,
|
|
101
|
+
# replace_line_schema,
|
|
102
|
+
# replace_lines_schema,
|
|
103
|
+
# indent_lines_schema,
|
|
104
|
+
# delete_line_schema,
|
|
105
|
+
# delete_lines_schema,
|
|
106
|
+
# undo_change_schema,
|
|
107
|
+
# list_changes_schema,
|
|
108
|
+
# extract_lines_schema,
|
|
109
|
+
# show_numbered_context_schema,
|
|
110
|
+
|
|
59
111
|
|
|
60
112
|
class NavigatorCoder(Coder):
|
|
61
113
|
"""Mode where the LLM autonomously manages which files are in context."""
|
|
@@ -75,6 +127,29 @@ class NavigatorCoder(Coder):
|
|
|
75
127
|
# Dictionary to track recently removed files
|
|
76
128
|
self.recently_removed = {}
|
|
77
129
|
|
|
130
|
+
# Tool usage history
|
|
131
|
+
self.tool_usage_history = []
|
|
132
|
+
self.tool_usage_retries = 10
|
|
133
|
+
self.read_tools = {
|
|
134
|
+
"viewfilesatglob",
|
|
135
|
+
"viewfilesmatching",
|
|
136
|
+
"ls",
|
|
137
|
+
"viewfileswithsymbol",
|
|
138
|
+
"grep",
|
|
139
|
+
"listchanges",
|
|
140
|
+
"extractlines",
|
|
141
|
+
"shownumberedcontext",
|
|
142
|
+
}
|
|
143
|
+
self.write_tools = {
|
|
144
|
+
"command",
|
|
145
|
+
"commandinteractive",
|
|
146
|
+
"insertblock",
|
|
147
|
+
"replaceblock",
|
|
148
|
+
"replaceall",
|
|
149
|
+
"replacetext",
|
|
150
|
+
"undochange",
|
|
151
|
+
}
|
|
152
|
+
|
|
78
153
|
# Configuration parameters
|
|
79
154
|
self.max_tool_calls = 100 # Maximum number of tool calls per response
|
|
80
155
|
|
|
@@ -110,11 +185,42 @@ class NavigatorCoder(Coder):
|
|
|
110
185
|
self.tokens_calculated = False
|
|
111
186
|
|
|
112
187
|
super().__init__(*args, **kwargs)
|
|
113
|
-
self.initialize_local_tools()
|
|
114
188
|
|
|
115
|
-
def
|
|
116
|
-
|
|
117
|
-
|
|
189
|
+
def get_local_tool_schemas(self):
|
|
190
|
+
"""Returns the JSON schemas for all local tools."""
|
|
191
|
+
return [
|
|
192
|
+
view_files_matching_schema,
|
|
193
|
+
ls_schema,
|
|
194
|
+
view_schema,
|
|
195
|
+
remove_schema,
|
|
196
|
+
make_editable_schema,
|
|
197
|
+
make_readonly_schema,
|
|
198
|
+
view_files_with_symbol_schema,
|
|
199
|
+
command_schema,
|
|
200
|
+
command_interactive_schema,
|
|
201
|
+
grep_schema,
|
|
202
|
+
replace_text_schema,
|
|
203
|
+
replace_all_schema,
|
|
204
|
+
insert_block_schema,
|
|
205
|
+
delete_block_schema,
|
|
206
|
+
replace_line_schema,
|
|
207
|
+
replace_lines_schema,
|
|
208
|
+
indent_lines_schema,
|
|
209
|
+
delete_line_schema,
|
|
210
|
+
delete_lines_schema,
|
|
211
|
+
undo_change_schema,
|
|
212
|
+
list_changes_schema,
|
|
213
|
+
extract_lines_schema,
|
|
214
|
+
show_numbered_context_schema,
|
|
215
|
+
update_todo_list_schema,
|
|
216
|
+
git_diff_schema,
|
|
217
|
+
git_log_schema,
|
|
218
|
+
git_show_schema,
|
|
219
|
+
git_status_schema,
|
|
220
|
+
]
|
|
221
|
+
|
|
222
|
+
async def initialize_mcp_tools(self):
|
|
223
|
+
await super().initialize_mcp_tools()
|
|
118
224
|
|
|
119
225
|
local_tools = self.get_local_tool_schemas()
|
|
120
226
|
if not local_tools:
|
|
@@ -133,491 +239,6 @@ class NavigatorCoder(Coder):
|
|
|
133
239
|
|
|
134
240
|
if "local_tools" not in [name for name, _ in self.mcp_tools]:
|
|
135
241
|
self.mcp_tools.append((local_server.name, local_tools))
|
|
136
|
-
self.functions = self.get_tool_list()
|
|
137
|
-
|
|
138
|
-
def get_local_tool_schemas(self):
|
|
139
|
-
"""Returns the JSON schemas for all local tools."""
|
|
140
|
-
return [
|
|
141
|
-
{
|
|
142
|
-
"type": "function",
|
|
143
|
-
"function": {
|
|
144
|
-
"name": "ViewFilesAtGlob",
|
|
145
|
-
"description": "View files matching a glob pattern.",
|
|
146
|
-
"parameters": {
|
|
147
|
-
"type": "object",
|
|
148
|
-
"properties": {
|
|
149
|
-
"pattern": {
|
|
150
|
-
"type": "string",
|
|
151
|
-
"description": "The glob pattern to match files.",
|
|
152
|
-
},
|
|
153
|
-
},
|
|
154
|
-
"required": ["pattern"],
|
|
155
|
-
},
|
|
156
|
-
},
|
|
157
|
-
},
|
|
158
|
-
{
|
|
159
|
-
"type": "function",
|
|
160
|
-
"function": {
|
|
161
|
-
"name": "ViewFilesMatching",
|
|
162
|
-
"description": "View files containing a specific pattern.",
|
|
163
|
-
"parameters": {
|
|
164
|
-
"type": "object",
|
|
165
|
-
"properties": {
|
|
166
|
-
"pattern": {
|
|
167
|
-
"type": "string",
|
|
168
|
-
"description": "The pattern to search for in file contents.",
|
|
169
|
-
},
|
|
170
|
-
"file_pattern": {
|
|
171
|
-
"type": "string",
|
|
172
|
-
"description": (
|
|
173
|
-
"An optional glob pattern to filter which files are searched."
|
|
174
|
-
),
|
|
175
|
-
},
|
|
176
|
-
"regex": {
|
|
177
|
-
"type": "boolean",
|
|
178
|
-
"description": (
|
|
179
|
-
"Whether the pattern is a regular expression. Defaults to"
|
|
180
|
-
" False."
|
|
181
|
-
),
|
|
182
|
-
},
|
|
183
|
-
},
|
|
184
|
-
"required": ["pattern"],
|
|
185
|
-
},
|
|
186
|
-
},
|
|
187
|
-
},
|
|
188
|
-
{
|
|
189
|
-
"type": "function",
|
|
190
|
-
"function": {
|
|
191
|
-
"name": "Ls",
|
|
192
|
-
"description": "List files in a directory.",
|
|
193
|
-
"parameters": {
|
|
194
|
-
"type": "object",
|
|
195
|
-
"properties": {
|
|
196
|
-
"directory": {
|
|
197
|
-
"type": "string",
|
|
198
|
-
"description": "The directory to list.",
|
|
199
|
-
},
|
|
200
|
-
},
|
|
201
|
-
"required": ["directory"],
|
|
202
|
-
},
|
|
203
|
-
},
|
|
204
|
-
},
|
|
205
|
-
{
|
|
206
|
-
"type": "function",
|
|
207
|
-
"function": {
|
|
208
|
-
"name": "View",
|
|
209
|
-
"description": "View a specific file.",
|
|
210
|
-
"parameters": {
|
|
211
|
-
"type": "object",
|
|
212
|
-
"properties": {
|
|
213
|
-
"file_path": {
|
|
214
|
-
"type": "string",
|
|
215
|
-
"description": "The path to the file to view.",
|
|
216
|
-
},
|
|
217
|
-
},
|
|
218
|
-
"required": ["file_path"],
|
|
219
|
-
},
|
|
220
|
-
},
|
|
221
|
-
},
|
|
222
|
-
{
|
|
223
|
-
"type": "function",
|
|
224
|
-
"function": {
|
|
225
|
-
"name": "Remove",
|
|
226
|
-
"description": "Remove a file from the chat context.",
|
|
227
|
-
"parameters": {
|
|
228
|
-
"type": "object",
|
|
229
|
-
"properties": {
|
|
230
|
-
"file_path": {
|
|
231
|
-
"type": "string",
|
|
232
|
-
"description": "The path to the file to remove.",
|
|
233
|
-
},
|
|
234
|
-
},
|
|
235
|
-
"required": ["file_path"],
|
|
236
|
-
},
|
|
237
|
-
},
|
|
238
|
-
},
|
|
239
|
-
{
|
|
240
|
-
"type": "function",
|
|
241
|
-
"function": {
|
|
242
|
-
"name": "MakeEditable",
|
|
243
|
-
"description": "Make a read-only file editable.",
|
|
244
|
-
"parameters": {
|
|
245
|
-
"type": "object",
|
|
246
|
-
"properties": {
|
|
247
|
-
"file_path": {
|
|
248
|
-
"type": "string",
|
|
249
|
-
"description": "The path to the file to make editable.",
|
|
250
|
-
},
|
|
251
|
-
},
|
|
252
|
-
"required": ["file_path"],
|
|
253
|
-
},
|
|
254
|
-
},
|
|
255
|
-
},
|
|
256
|
-
{
|
|
257
|
-
"type": "function",
|
|
258
|
-
"function": {
|
|
259
|
-
"name": "MakeReadonly",
|
|
260
|
-
"description": "Make an editable file read-only.",
|
|
261
|
-
"parameters": {
|
|
262
|
-
"type": "object",
|
|
263
|
-
"properties": {
|
|
264
|
-
"file_path": {
|
|
265
|
-
"type": "string",
|
|
266
|
-
"description": "The path to the file to make read-only.",
|
|
267
|
-
},
|
|
268
|
-
},
|
|
269
|
-
"required": ["file_path"],
|
|
270
|
-
},
|
|
271
|
-
},
|
|
272
|
-
},
|
|
273
|
-
{
|
|
274
|
-
"type": "function",
|
|
275
|
-
"function": {
|
|
276
|
-
"name": "ViewFilesWithSymbol",
|
|
277
|
-
"description": (
|
|
278
|
-
"View files that contain a specific symbol (e.g., class, function)."
|
|
279
|
-
),
|
|
280
|
-
"parameters": {
|
|
281
|
-
"type": "object",
|
|
282
|
-
"properties": {
|
|
283
|
-
"symbol": {
|
|
284
|
-
"type": "string",
|
|
285
|
-
"description": "The symbol to search for.",
|
|
286
|
-
},
|
|
287
|
-
},
|
|
288
|
-
"required": ["symbol"],
|
|
289
|
-
},
|
|
290
|
-
},
|
|
291
|
-
},
|
|
292
|
-
{
|
|
293
|
-
"type": "function",
|
|
294
|
-
"function": {
|
|
295
|
-
"name": "Command",
|
|
296
|
-
"description": "Execute a shell command.",
|
|
297
|
-
"parameters": {
|
|
298
|
-
"type": "object",
|
|
299
|
-
"properties": {
|
|
300
|
-
"command_string": {
|
|
301
|
-
"type": "string",
|
|
302
|
-
"description": "The shell command to execute.",
|
|
303
|
-
},
|
|
304
|
-
},
|
|
305
|
-
"required": ["command_string"],
|
|
306
|
-
},
|
|
307
|
-
},
|
|
308
|
-
},
|
|
309
|
-
{
|
|
310
|
-
"type": "function",
|
|
311
|
-
"function": {
|
|
312
|
-
"name": "CommandInteractive",
|
|
313
|
-
"description": "Execute a shell command interactively.",
|
|
314
|
-
"parameters": {
|
|
315
|
-
"type": "object",
|
|
316
|
-
"properties": {
|
|
317
|
-
"command_string": {
|
|
318
|
-
"type": "string",
|
|
319
|
-
"description": "The interactive shell command to execute.",
|
|
320
|
-
},
|
|
321
|
-
},
|
|
322
|
-
"required": ["command_string"],
|
|
323
|
-
},
|
|
324
|
-
},
|
|
325
|
-
},
|
|
326
|
-
{
|
|
327
|
-
"type": "function",
|
|
328
|
-
"function": {
|
|
329
|
-
"name": "Grep",
|
|
330
|
-
"description": "Search for a pattern in files.",
|
|
331
|
-
"parameters": {
|
|
332
|
-
"type": "object",
|
|
333
|
-
"properties": {
|
|
334
|
-
"pattern": {
|
|
335
|
-
"type": "string",
|
|
336
|
-
"description": "The pattern to search for.",
|
|
337
|
-
},
|
|
338
|
-
"file_pattern": {
|
|
339
|
-
"type": "string",
|
|
340
|
-
"description": "Glob pattern for files to search. Defaults to '*'.",
|
|
341
|
-
},
|
|
342
|
-
"directory": {
|
|
343
|
-
"type": "string",
|
|
344
|
-
"description": "Directory to search in. Defaults to '.'.",
|
|
345
|
-
},
|
|
346
|
-
"use_regex": {
|
|
347
|
-
"type": "boolean",
|
|
348
|
-
"description": "Whether to use regex. Defaults to False.",
|
|
349
|
-
},
|
|
350
|
-
"case_insensitive": {
|
|
351
|
-
"type": "boolean",
|
|
352
|
-
"description": (
|
|
353
|
-
"Whether to perform a case-insensitive search. Defaults to"
|
|
354
|
-
" False."
|
|
355
|
-
),
|
|
356
|
-
},
|
|
357
|
-
"context_before": {
|
|
358
|
-
"type": "integer",
|
|
359
|
-
"description": (
|
|
360
|
-
"Number of lines to show before a match. Defaults to 5."
|
|
361
|
-
),
|
|
362
|
-
},
|
|
363
|
-
"context_after": {
|
|
364
|
-
"type": "integer",
|
|
365
|
-
"description": (
|
|
366
|
-
"Number of lines to show after a match. Defaults to 5."
|
|
367
|
-
),
|
|
368
|
-
},
|
|
369
|
-
},
|
|
370
|
-
"required": ["pattern"],
|
|
371
|
-
},
|
|
372
|
-
},
|
|
373
|
-
},
|
|
374
|
-
{
|
|
375
|
-
"type": "function",
|
|
376
|
-
"function": {
|
|
377
|
-
"name": "ReplaceText",
|
|
378
|
-
"description": "Replace text in a file.",
|
|
379
|
-
"parameters": {
|
|
380
|
-
"type": "object",
|
|
381
|
-
"properties": {
|
|
382
|
-
"file_path": {"type": "string"},
|
|
383
|
-
"find_text": {"type": "string"},
|
|
384
|
-
"replace_text": {"type": "string"},
|
|
385
|
-
"near_context": {"type": "string"},
|
|
386
|
-
"occurrence": {"type": "integer", "default": 1},
|
|
387
|
-
"change_id": {"type": "string"},
|
|
388
|
-
"dry_run": {"type": "boolean", "default": False},
|
|
389
|
-
},
|
|
390
|
-
"required": ["file_path", "find_text", "replace_text"],
|
|
391
|
-
},
|
|
392
|
-
},
|
|
393
|
-
},
|
|
394
|
-
{
|
|
395
|
-
"type": "function",
|
|
396
|
-
"function": {
|
|
397
|
-
"name": "ReplaceAll",
|
|
398
|
-
"description": "Replace all occurrences of text in a file.",
|
|
399
|
-
"parameters": {
|
|
400
|
-
"type": "object",
|
|
401
|
-
"properties": {
|
|
402
|
-
"file_path": {"type": "string"},
|
|
403
|
-
"find_text": {"type": "string"},
|
|
404
|
-
"replace_text": {"type": "string"},
|
|
405
|
-
"change_id": {"type": "string"},
|
|
406
|
-
"dry_run": {"type": "boolean", "default": False},
|
|
407
|
-
},
|
|
408
|
-
"required": ["file_path", "find_text", "replace_text"],
|
|
409
|
-
},
|
|
410
|
-
},
|
|
411
|
-
},
|
|
412
|
-
{
|
|
413
|
-
"type": "function",
|
|
414
|
-
"function": {
|
|
415
|
-
"name": "InsertBlock",
|
|
416
|
-
"description": "Insert a block of content into a file.",
|
|
417
|
-
"parameters": {
|
|
418
|
-
"type": "object",
|
|
419
|
-
"properties": {
|
|
420
|
-
"file_path": {"type": "string"},
|
|
421
|
-
"content": {"type": "string"},
|
|
422
|
-
"after_pattern": {"type": "string"},
|
|
423
|
-
"before_pattern": {"type": "string"},
|
|
424
|
-
"occurrence": {"type": "integer", "default": 1},
|
|
425
|
-
"change_id": {"type": "string"},
|
|
426
|
-
"dry_run": {"type": "boolean", "default": False},
|
|
427
|
-
"position": {"type": "string", "enum": ["top", "bottom"]},
|
|
428
|
-
"auto_indent": {"type": "boolean", "default": True},
|
|
429
|
-
"use_regex": {"type": "boolean", "default": False},
|
|
430
|
-
},
|
|
431
|
-
"required": ["file_path", "content"],
|
|
432
|
-
},
|
|
433
|
-
},
|
|
434
|
-
},
|
|
435
|
-
{
|
|
436
|
-
"type": "function",
|
|
437
|
-
"function": {
|
|
438
|
-
"name": "DeleteBlock",
|
|
439
|
-
"description": "Delete a block of lines from a file.",
|
|
440
|
-
"parameters": {
|
|
441
|
-
"type": "object",
|
|
442
|
-
"properties": {
|
|
443
|
-
"file_path": {"type": "string"},
|
|
444
|
-
"start_pattern": {"type": "string"},
|
|
445
|
-
"end_pattern": {"type": "string"},
|
|
446
|
-
"line_count": {"type": "integer"},
|
|
447
|
-
"near_context": {"type": "string"},
|
|
448
|
-
"occurrence": {"type": "integer", "default": 1},
|
|
449
|
-
"change_id": {"type": "string"},
|
|
450
|
-
"dry_run": {"type": "boolean", "default": False},
|
|
451
|
-
},
|
|
452
|
-
"required": ["file_path", "start_pattern"],
|
|
453
|
-
},
|
|
454
|
-
},
|
|
455
|
-
},
|
|
456
|
-
{
|
|
457
|
-
"type": "function",
|
|
458
|
-
"function": {
|
|
459
|
-
"name": "ReplaceLine",
|
|
460
|
-
"description": "Replace a single line in a file.",
|
|
461
|
-
"parameters": {
|
|
462
|
-
"type": "object",
|
|
463
|
-
"properties": {
|
|
464
|
-
"file_path": {"type": "string"},
|
|
465
|
-
"line_number": {"type": "integer"},
|
|
466
|
-
"new_content": {"type": "string"},
|
|
467
|
-
"change_id": {"type": "string"},
|
|
468
|
-
"dry_run": {"type": "boolean", "default": False},
|
|
469
|
-
},
|
|
470
|
-
"required": ["file_path", "line_number", "new_content"],
|
|
471
|
-
},
|
|
472
|
-
},
|
|
473
|
-
},
|
|
474
|
-
{
|
|
475
|
-
"type": "function",
|
|
476
|
-
"function": {
|
|
477
|
-
"name": "ReplaceLines",
|
|
478
|
-
"description": "Replace a range of lines in a file.",
|
|
479
|
-
"parameters": {
|
|
480
|
-
"type": "object",
|
|
481
|
-
"properties": {
|
|
482
|
-
"file_path": {"type": "string"},
|
|
483
|
-
"start_line": {"type": "integer"},
|
|
484
|
-
"end_line": {"type": "integer"},
|
|
485
|
-
"new_content": {"type": "string"},
|
|
486
|
-
"change_id": {"type": "string"},
|
|
487
|
-
"dry_run": {"type": "boolean", "default": False},
|
|
488
|
-
},
|
|
489
|
-
"required": ["file_path", "start_line", "end_line", "new_content"],
|
|
490
|
-
},
|
|
491
|
-
},
|
|
492
|
-
},
|
|
493
|
-
{
|
|
494
|
-
"type": "function",
|
|
495
|
-
"function": {
|
|
496
|
-
"name": "IndentLines",
|
|
497
|
-
"description": "Indent a block of lines in a file.",
|
|
498
|
-
"parameters": {
|
|
499
|
-
"type": "object",
|
|
500
|
-
"properties": {
|
|
501
|
-
"file_path": {"type": "string"},
|
|
502
|
-
"start_pattern": {"type": "string"},
|
|
503
|
-
"end_pattern": {"type": "string"},
|
|
504
|
-
"line_count": {"type": "integer"},
|
|
505
|
-
"indent_levels": {"type": "integer", "default": 1},
|
|
506
|
-
"near_context": {"type": "string"},
|
|
507
|
-
"occurrence": {"type": "integer", "default": 1},
|
|
508
|
-
"change_id": {"type": "string"},
|
|
509
|
-
"dry_run": {"type": "boolean", "default": False},
|
|
510
|
-
},
|
|
511
|
-
"required": ["file_path", "start_pattern"],
|
|
512
|
-
},
|
|
513
|
-
},
|
|
514
|
-
},
|
|
515
|
-
{
|
|
516
|
-
"type": "function",
|
|
517
|
-
"function": {
|
|
518
|
-
"name": "DeleteLine",
|
|
519
|
-
"description": "Delete a single line from a file.",
|
|
520
|
-
"parameters": {
|
|
521
|
-
"type": "object",
|
|
522
|
-
"properties": {
|
|
523
|
-
"file_path": {"type": "string"},
|
|
524
|
-
"line_number": {"type": "integer"},
|
|
525
|
-
"change_id": {"type": "string"},
|
|
526
|
-
"dry_run": {"type": "boolean", "default": False},
|
|
527
|
-
},
|
|
528
|
-
"required": ["file_path", "line_number"],
|
|
529
|
-
},
|
|
530
|
-
},
|
|
531
|
-
},
|
|
532
|
-
{
|
|
533
|
-
"type": "function",
|
|
534
|
-
"function": {
|
|
535
|
-
"name": "DeleteLines",
|
|
536
|
-
"description": "Delete a range of lines from a file.",
|
|
537
|
-
"parameters": {
|
|
538
|
-
"type": "object",
|
|
539
|
-
"properties": {
|
|
540
|
-
"file_path": {"type": "string"},
|
|
541
|
-
"start_line": {"type": "integer"},
|
|
542
|
-
"end_line": {"type": "integer"},
|
|
543
|
-
"change_id": {"type": "string"},
|
|
544
|
-
"dry_run": {"type": "boolean", "default": False},
|
|
545
|
-
},
|
|
546
|
-
"required": ["file_path", "start_line", "end_line"],
|
|
547
|
-
},
|
|
548
|
-
},
|
|
549
|
-
},
|
|
550
|
-
{
|
|
551
|
-
"type": "function",
|
|
552
|
-
"function": {
|
|
553
|
-
"name": "UndoChange",
|
|
554
|
-
"description": "Undo a previously applied change.",
|
|
555
|
-
"parameters": {
|
|
556
|
-
"type": "object",
|
|
557
|
-
"properties": {
|
|
558
|
-
"change_id": {"type": "string"},
|
|
559
|
-
"file_path": {"type": "string"},
|
|
560
|
-
},
|
|
561
|
-
},
|
|
562
|
-
},
|
|
563
|
-
},
|
|
564
|
-
{
|
|
565
|
-
"type": "function",
|
|
566
|
-
"function": {
|
|
567
|
-
"name": "ListChanges",
|
|
568
|
-
"description": "List recent changes made.",
|
|
569
|
-
"parameters": {
|
|
570
|
-
"type": "object",
|
|
571
|
-
"properties": {
|
|
572
|
-
"file_path": {"type": "string"},
|
|
573
|
-
"limit": {"type": "integer", "default": 10},
|
|
574
|
-
},
|
|
575
|
-
},
|
|
576
|
-
},
|
|
577
|
-
},
|
|
578
|
-
{
|
|
579
|
-
"type": "function",
|
|
580
|
-
"function": {
|
|
581
|
-
"name": "ExtractLines",
|
|
582
|
-
"description": (
|
|
583
|
-
"Extract lines from a source file and append them to a target file."
|
|
584
|
-
),
|
|
585
|
-
"parameters": {
|
|
586
|
-
"type": "object",
|
|
587
|
-
"properties": {
|
|
588
|
-
"source_file_path": {"type": "string"},
|
|
589
|
-
"target_file_path": {"type": "string"},
|
|
590
|
-
"start_pattern": {"type": "string"},
|
|
591
|
-
"end_pattern": {"type": "string"},
|
|
592
|
-
"line_count": {"type": "integer"},
|
|
593
|
-
"near_context": {"type": "string"},
|
|
594
|
-
"occurrence": {"type": "integer", "default": 1},
|
|
595
|
-
"dry_run": {"type": "boolean", "default": False},
|
|
596
|
-
},
|
|
597
|
-
"required": ["source_file_path", "target_file_path", "start_pattern"],
|
|
598
|
-
},
|
|
599
|
-
},
|
|
600
|
-
},
|
|
601
|
-
{
|
|
602
|
-
"type": "function",
|
|
603
|
-
"function": {
|
|
604
|
-
"name": "ShowNumberedContext",
|
|
605
|
-
"description": (
|
|
606
|
-
"Show numbered lines of context around a pattern or line number."
|
|
607
|
-
),
|
|
608
|
-
"parameters": {
|
|
609
|
-
"type": "object",
|
|
610
|
-
"properties": {
|
|
611
|
-
"file_path": {"type": "string"},
|
|
612
|
-
"pattern": {"type": "string"},
|
|
613
|
-
"line_number": {"type": "integer"},
|
|
614
|
-
"context_lines": {"type": "integer", "default": 3},
|
|
615
|
-
},
|
|
616
|
-
"required": ["file_path"],
|
|
617
|
-
},
|
|
618
|
-
},
|
|
619
|
-
},
|
|
620
|
-
]
|
|
621
242
|
|
|
622
243
|
async def _execute_local_tool_calls(self, tool_calls_list):
|
|
623
244
|
tool_responses = []
|
|
@@ -646,61 +267,52 @@ class NavigatorCoder(Coder):
|
|
|
646
267
|
all_results_content = []
|
|
647
268
|
norm_tool_name = tool_name.lower()
|
|
648
269
|
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
single_result = _execute_delete_lines(self, **params)
|
|
692
|
-
elif norm_tool_name == "undochange":
|
|
693
|
-
single_result = _execute_undo_change(self, **params)
|
|
694
|
-
elif norm_tool_name == "listchanges":
|
|
695
|
-
single_result = _execute_list_changes(self, **params)
|
|
696
|
-
elif norm_tool_name == "extractlines":
|
|
697
|
-
single_result = _execute_extract_lines(self, **params)
|
|
698
|
-
elif norm_tool_name == "shownumberedcontext":
|
|
699
|
-
single_result = execute_show_numbered_context(self, **params)
|
|
700
|
-
else:
|
|
701
|
-
single_result = f"Error: Unknown local tool name '{tool_name}'"
|
|
270
|
+
tasks = []
|
|
271
|
+
tool_functions = {
|
|
272
|
+
"viewfilesmatching": execute_view_files_matching,
|
|
273
|
+
"ls": execute_ls,
|
|
274
|
+
"view": execute_view,
|
|
275
|
+
"remove": _execute_remove,
|
|
276
|
+
"makeeditable": _execute_make_editable,
|
|
277
|
+
"makereadonly": _execute_make_readonly,
|
|
278
|
+
"viewfileswithsymbol": _execute_view_files_with_symbol,
|
|
279
|
+
"command": _execute_command,
|
|
280
|
+
"commandinteractive": _execute_command_interactive,
|
|
281
|
+
"grep": _execute_grep,
|
|
282
|
+
"replacetext": _execute_replace_text,
|
|
283
|
+
"replaceall": _execute_replace_all,
|
|
284
|
+
"insertblock": _execute_insert_block,
|
|
285
|
+
"deleteblock": _execute_delete_block,
|
|
286
|
+
"replaceline": _execute_replace_line,
|
|
287
|
+
"replacelines": _execute_replace_lines,
|
|
288
|
+
"indentlines": _execute_indent_lines,
|
|
289
|
+
"deleteline": _execute_delete_line,
|
|
290
|
+
"deletelines": _execute_delete_lines,
|
|
291
|
+
"undochange": _execute_undo_change,
|
|
292
|
+
"listchanges": _execute_list_changes,
|
|
293
|
+
"extractlines": _execute_extract_lines,
|
|
294
|
+
"shownumberedcontext": execute_show_numbered_context,
|
|
295
|
+
"updatetodolist": _execute_update_todo_list,
|
|
296
|
+
"git_diff": _execute_git_diff,
|
|
297
|
+
"git_log": _execute_git_log,
|
|
298
|
+
"git_show": _execute_git_show,
|
|
299
|
+
"git_status": _execute_git_status,
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
func = tool_functions.get(norm_tool_name)
|
|
303
|
+
|
|
304
|
+
if func:
|
|
305
|
+
for params in parsed_args_list:
|
|
306
|
+
if asyncio.iscoroutinefunction(func):
|
|
307
|
+
tasks.append(func(self, **params))
|
|
308
|
+
else:
|
|
309
|
+
tasks.append(asyncio.to_thread(func, self, **params))
|
|
310
|
+
else:
|
|
311
|
+
all_results_content.append(f"Error: Unknown local tool name '{tool_name}'")
|
|
702
312
|
|
|
703
|
-
|
|
313
|
+
if tasks:
|
|
314
|
+
task_results = await asyncio.gather(*tasks)
|
|
315
|
+
all_results_content.extend(str(res) for res in task_results)
|
|
704
316
|
|
|
705
317
|
result_message = "\n\n".join(all_results_content)
|
|
706
318
|
|
|
@@ -714,13 +326,12 @@ class NavigatorCoder(Coder):
|
|
|
714
326
|
{
|
|
715
327
|
"role": "tool",
|
|
716
328
|
"tool_call_id": tool_call.id,
|
|
717
|
-
"name": tool_name,
|
|
718
329
|
"content": result_message,
|
|
719
330
|
}
|
|
720
331
|
)
|
|
721
332
|
return tool_responses
|
|
722
333
|
|
|
723
|
-
def _execute_mcp_tool(self, server, tool_name, params):
|
|
334
|
+
async def _execute_mcp_tool(self, server, tool_name, params):
|
|
724
335
|
"""Helper to execute a single MCP tool call, created from legacy format."""
|
|
725
336
|
|
|
726
337
|
# This is a simplified, synchronous wrapper around async logic
|
|
@@ -767,10 +378,8 @@ class NavigatorCoder(Coder):
|
|
|
767
378
|
f"Executing {tool_name} on {server.name} failed: \n Error: {e}\n"
|
|
768
379
|
)
|
|
769
380
|
return f"Error executing tool call {tool_name}: {e}"
|
|
770
|
-
finally:
|
|
771
|
-
await server.disconnect()
|
|
772
381
|
|
|
773
|
-
return
|
|
382
|
+
return await _exec_async()
|
|
774
383
|
|
|
775
384
|
def _calculate_context_block_tokens(self, force=False):
|
|
776
385
|
"""
|
|
@@ -840,6 +449,8 @@ class NavigatorCoder(Coder):
|
|
|
840
449
|
content = self.get_context_symbol_outline()
|
|
841
450
|
elif block_name == "context_summary":
|
|
842
451
|
content = self.get_context_summary()
|
|
452
|
+
elif block_name == "todo_list":
|
|
453
|
+
content = self.get_todo_list()
|
|
843
454
|
|
|
844
455
|
# Cache the result if it's not None
|
|
845
456
|
if content is not None:
|
|
@@ -965,63 +576,11 @@ class NavigatorCoder(Coder):
|
|
|
965
576
|
|
|
966
577
|
This approach preserves prefix caching while providing fresh context information.
|
|
967
578
|
"""
|
|
968
|
-
#
|
|
969
|
-
# We'll manually build the chunks to control placement of context blocks
|
|
970
|
-
chunks = self.format_chat_chunks_base()
|
|
971
|
-
|
|
972
|
-
# If enhanced context blocks are not enabled, just return the base chunks
|
|
579
|
+
# If enhanced context blocks are not enabled, use the base implementation
|
|
973
580
|
if not self.use_enhanced_context:
|
|
974
|
-
return
|
|
975
|
-
|
|
976
|
-
# Make sure token counts are updated - using centralized method
|
|
977
|
-
# This also populates the context block cache
|
|
978
|
-
self._calculate_context_block_tokens()
|
|
581
|
+
return super().format_chat_chunks()
|
|
979
582
|
|
|
980
|
-
#
|
|
981
|
-
env_context = self.get_cached_context_block("environment_info")
|
|
982
|
-
dir_structure = self.get_cached_context_block("directory_structure")
|
|
983
|
-
git_status = self.get_cached_context_block("git_status")
|
|
984
|
-
symbol_outline = self.get_cached_context_block("symbol_outline")
|
|
985
|
-
|
|
986
|
-
# Context summary needs special handling because it depends on other blocks
|
|
987
|
-
context_summary = self.get_context_summary()
|
|
988
|
-
|
|
989
|
-
# 1. Add relatively static blocks BEFORE done_messages
|
|
990
|
-
# These blocks change less frequently and can be part of the cacheable prefix
|
|
991
|
-
static_blocks = []
|
|
992
|
-
if dir_structure:
|
|
993
|
-
static_blocks.append(dir_structure)
|
|
994
|
-
if env_context:
|
|
995
|
-
static_blocks.append(env_context)
|
|
996
|
-
|
|
997
|
-
if static_blocks:
|
|
998
|
-
static_message = "\n\n".join(static_blocks)
|
|
999
|
-
# Insert as a system message right before done_messages
|
|
1000
|
-
chunks.done.insert(0, dict(role="system", content=static_message))
|
|
1001
|
-
|
|
1002
|
-
# 2. Add dynamic blocks AFTER chat_files
|
|
1003
|
-
# These blocks change with the current files in context
|
|
1004
|
-
dynamic_blocks = []
|
|
1005
|
-
if context_summary:
|
|
1006
|
-
dynamic_blocks.append(context_summary)
|
|
1007
|
-
if symbol_outline:
|
|
1008
|
-
dynamic_blocks.append(symbol_outline)
|
|
1009
|
-
if git_status:
|
|
1010
|
-
dynamic_blocks.append(git_status)
|
|
1011
|
-
|
|
1012
|
-
if dynamic_blocks:
|
|
1013
|
-
dynamic_message = "\n\n".join(dynamic_blocks)
|
|
1014
|
-
# Append as a system message after chat_files
|
|
1015
|
-
chunks.chat_files.append(dict(role="system", content=dynamic_message))
|
|
1016
|
-
|
|
1017
|
-
return chunks
|
|
1018
|
-
|
|
1019
|
-
def format_chat_chunks_base(self):
|
|
1020
|
-
"""
|
|
1021
|
-
Create base chat chunks without enhanced context blocks.
|
|
1022
|
-
This is a copy of the parent's format_chat_chunks method to avoid
|
|
1023
|
-
calling super() which would create a recursive loop.
|
|
1024
|
-
"""
|
|
583
|
+
# Build chunks from scratch to avoid duplication with enhanced context blocks
|
|
1025
584
|
self.choose_fence()
|
|
1026
585
|
main_sys = self.fmt_system_prompt(self.gpt_prompts.main_system)
|
|
1027
586
|
|
|
@@ -1072,12 +631,65 @@ class NavigatorCoder(Coder):
|
|
|
1072
631
|
chunks.examples = example_messages
|
|
1073
632
|
|
|
1074
633
|
self.summarize_end()
|
|
1075
|
-
chunks.done = self.done_messages
|
|
634
|
+
chunks.done = list(self.done_messages)
|
|
1076
635
|
|
|
1077
636
|
chunks.repo = self.get_repo_messages()
|
|
1078
637
|
chunks.readonly_files = self.get_readonly_files_messages()
|
|
1079
638
|
chunks.chat_files = self.get_chat_files_messages()
|
|
1080
639
|
|
|
640
|
+
# Make sure token counts are updated - using centralized method
|
|
641
|
+
# This also populates the context block cache
|
|
642
|
+
self._calculate_context_block_tokens()
|
|
643
|
+
|
|
644
|
+
# Get blocks from cache to avoid regenerating them
|
|
645
|
+
env_context = self.get_cached_context_block("environment_info")
|
|
646
|
+
dir_structure = self.get_cached_context_block("directory_structure")
|
|
647
|
+
git_status = self.get_cached_context_block("git_status")
|
|
648
|
+
symbol_outline = self.get_cached_context_block("symbol_outline")
|
|
649
|
+
todo_list = self.get_cached_context_block("todo_list")
|
|
650
|
+
|
|
651
|
+
# Context summary needs special handling because it depends on other blocks
|
|
652
|
+
context_summary = self.get_context_summary()
|
|
653
|
+
|
|
654
|
+
# 1. Add relatively static blocks BEFORE done_messages
|
|
655
|
+
# These blocks change less frequently and can be part of the cacheable prefix
|
|
656
|
+
static_blocks = []
|
|
657
|
+
if dir_structure:
|
|
658
|
+
static_blocks.append(dir_structure)
|
|
659
|
+
if env_context:
|
|
660
|
+
static_blocks.append(env_context)
|
|
661
|
+
|
|
662
|
+
if static_blocks:
|
|
663
|
+
static_message = "\n\n".join(static_blocks)
|
|
664
|
+
# Insert as a system message right before done_messages
|
|
665
|
+
chunks.done.insert(0, dict(role="system", content=static_message))
|
|
666
|
+
|
|
667
|
+
# 2. Add dynamic blocks AFTER chat_files
|
|
668
|
+
# These blocks change with the current files in context
|
|
669
|
+
dynamic_blocks = []
|
|
670
|
+
if todo_list:
|
|
671
|
+
dynamic_blocks.append(todo_list)
|
|
672
|
+
if context_summary:
|
|
673
|
+
dynamic_blocks.append(context_summary)
|
|
674
|
+
if symbol_outline:
|
|
675
|
+
dynamic_blocks.append(symbol_outline)
|
|
676
|
+
if git_status:
|
|
677
|
+
dynamic_blocks.append(git_status)
|
|
678
|
+
|
|
679
|
+
# Add tool usage context if there are repetitive tools
|
|
680
|
+
if hasattr(self, "tool_usage_history") and self.tool_usage_history:
|
|
681
|
+
repetitive_tools = self._get_repetitive_tools()
|
|
682
|
+
if repetitive_tools:
|
|
683
|
+
tool_context = self._generate_tool_context(repetitive_tools)
|
|
684
|
+
if tool_context:
|
|
685
|
+
dynamic_blocks.append(tool_context)
|
|
686
|
+
|
|
687
|
+
if dynamic_blocks:
|
|
688
|
+
dynamic_message = "\n\n".join(dynamic_blocks)
|
|
689
|
+
# Append as a system message after chat_files
|
|
690
|
+
chunks.chat_files.append(dict(role="system", content=dynamic_message))
|
|
691
|
+
|
|
692
|
+
# Add reminder if needed
|
|
1081
693
|
if self.gpt_prompts.system_reminder:
|
|
1082
694
|
reminder_message = [
|
|
1083
695
|
dict(
|
|
@@ -1293,7 +905,21 @@ class NavigatorCoder(Coder):
|
|
|
1293
905
|
self.io.tool_error(f"Error generating environment info: {str(e)}")
|
|
1294
906
|
return None
|
|
1295
907
|
|
|
1296
|
-
def
|
|
908
|
+
async def process_tool_calls(self, tool_call_response):
|
|
909
|
+
"""
|
|
910
|
+
Track tool usage before calling the base implementation.
|
|
911
|
+
"""
|
|
912
|
+
|
|
913
|
+
if self.partial_response_tool_calls:
|
|
914
|
+
for tool_call in self.partial_response_tool_calls:
|
|
915
|
+
self.tool_usage_history.append(tool_call.get("function", {}).get("name"))
|
|
916
|
+
|
|
917
|
+
if len(self.tool_usage_history) > self.tool_usage_retries:
|
|
918
|
+
self.tool_usage_history.pop(0)
|
|
919
|
+
|
|
920
|
+
return await super().process_tool_calls(tool_call_response)
|
|
921
|
+
|
|
922
|
+
async def reply_completed(self):
|
|
1297
923
|
"""Process the completed response from the LLM.
|
|
1298
924
|
|
|
1299
925
|
This is a key method that:
|
|
@@ -1305,8 +931,9 @@ class NavigatorCoder(Coder):
|
|
|
1305
931
|
iteratively discover and analyze relevant files before providing
|
|
1306
932
|
a final answer to the user's question.
|
|
1307
933
|
"""
|
|
1308
|
-
# In granular editing mode, tool calls are handled by BaseCoder's process_tool_calls
|
|
1309
|
-
#
|
|
934
|
+
# In granular editing mode, tool calls are handled by BaseCoder's process_tool_calls,
|
|
935
|
+
# which is overridden in this class to track tool usage. This method is now only for
|
|
936
|
+
# legacy tool call format and search/replace blocks.
|
|
1310
937
|
if self.use_granular_editing:
|
|
1311
938
|
# Handle SEARCH/REPLACE blocks
|
|
1312
939
|
content = self.partial_response_content
|
|
@@ -1319,7 +946,7 @@ class NavigatorCoder(Coder):
|
|
|
1319
946
|
has_replace = ">>>>>>> REPLACE" in content
|
|
1320
947
|
if has_search and has_divider and has_replace:
|
|
1321
948
|
self.io.tool_output("Detected edit blocks, applying changes...")
|
|
1322
|
-
edited_files = self._apply_edits_from_response()
|
|
949
|
+
edited_files = await self._apply_edits_from_response()
|
|
1323
950
|
if self.reflected_message:
|
|
1324
951
|
return False # Trigger reflection if edits failed
|
|
1325
952
|
|
|
@@ -1335,14 +962,19 @@ class NavigatorCoder(Coder):
|
|
|
1335
962
|
# Legacy tool call processing for use_granular_editing=False
|
|
1336
963
|
content = self.partial_response_content
|
|
1337
964
|
if not content or not content.strip():
|
|
965
|
+
if len(self.tool_usage_history) > self.tool_usage_retries:
|
|
966
|
+
self.tool_usage_history = []
|
|
1338
967
|
return True
|
|
1339
968
|
original_content = content # Keep the original response
|
|
1340
969
|
|
|
1341
|
-
# Process tool commands: returns content with tool calls removed, results, flag if any tool calls were found
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
970
|
+
# Process tool commands: returns content with tool calls removed, results, flag if any tool calls were found
|
|
971
|
+
(
|
|
972
|
+
processed_content,
|
|
973
|
+
result_messages,
|
|
974
|
+
tool_calls_found,
|
|
975
|
+
content_before_last_separator,
|
|
976
|
+
tool_names_this_turn,
|
|
977
|
+
) = await self._process_tool_commands(content)
|
|
1346
978
|
|
|
1347
979
|
# Since we are no longer suppressing, the partial_response_content IS the final content.
|
|
1348
980
|
# We might want to update it to the processed_content (without tool calls) if we don't
|
|
@@ -1370,7 +1002,7 @@ class NavigatorCoder(Coder):
|
|
|
1370
1002
|
|
|
1371
1003
|
if edit_match:
|
|
1372
1004
|
self.io.tool_output("Detected edit blocks, applying changes within Navigator...")
|
|
1373
|
-
edited_files = self._apply_edits_from_response()
|
|
1005
|
+
edited_files = await self._apply_edits_from_response()
|
|
1374
1006
|
# If _apply_edits_from_response set a reflected_message (due to errors),
|
|
1375
1007
|
# return False to trigger a reflection loop.
|
|
1376
1008
|
if self.reflected_message:
|
|
@@ -1408,6 +1040,7 @@ class NavigatorCoder(Coder):
|
|
|
1408
1040
|
if tool_calls_found and self.num_reflections < self.max_reflections:
|
|
1409
1041
|
# Reset tool counter for next iteration
|
|
1410
1042
|
self.tool_call_count = 0
|
|
1043
|
+
|
|
1411
1044
|
# Clear exploration files for the next round
|
|
1412
1045
|
self.files_added_in_exploration = set()
|
|
1413
1046
|
|
|
@@ -1460,15 +1093,24 @@ class NavigatorCoder(Coder):
|
|
|
1460
1093
|
|
|
1461
1094
|
# After applying edits OR determining no edits were needed (and no reflection needed),
|
|
1462
1095
|
# the turn is complete. Reset counters and finalize history.
|
|
1096
|
+
|
|
1097
|
+
# Auto-commit any files edited by granular tools
|
|
1098
|
+
if self.files_edited_by_tools:
|
|
1099
|
+
saved_message = await self.auto_commit(self.files_edited_by_tools)
|
|
1100
|
+
if not saved_message and hasattr(self.gpt_prompts, "files_content_gpt_edits_no_repo"):
|
|
1101
|
+
saved_message = self.gpt_prompts.files_content_gpt_edits_no_repo
|
|
1102
|
+
self.move_back_cur_messages(saved_message)
|
|
1103
|
+
|
|
1463
1104
|
self.tool_call_count = 0
|
|
1464
1105
|
self.files_added_in_exploration = set()
|
|
1106
|
+
self.files_edited_by_tools = set()
|
|
1465
1107
|
# Move cur_messages to done_messages
|
|
1466
1108
|
self.move_back_cur_messages(
|
|
1467
1109
|
None
|
|
1468
1110
|
) # Pass None as we handled commit message earlier if needed
|
|
1469
1111
|
return True # Indicate exploration is finished for this round
|
|
1470
1112
|
|
|
1471
|
-
def _process_tool_commands(self, content):
|
|
1113
|
+
async def _process_tool_commands(self, content):
|
|
1472
1114
|
"""
|
|
1473
1115
|
Process tool commands in the `[tool_call(name, param=value)]` format within the content.
|
|
1474
1116
|
|
|
@@ -1485,6 +1127,7 @@ class NavigatorCoder(Coder):
|
|
|
1485
1127
|
tool_calls_found = False
|
|
1486
1128
|
call_count = 0
|
|
1487
1129
|
max_calls = self.max_tool_calls
|
|
1130
|
+
tool_names = []
|
|
1488
1131
|
|
|
1489
1132
|
# Check if there's a '---' separator and only process tool calls after the LAST one
|
|
1490
1133
|
separator_marker = "---"
|
|
@@ -1493,7 +1136,7 @@ class NavigatorCoder(Coder):
|
|
|
1493
1136
|
# If there's no separator, treat the entire content as before the separator
|
|
1494
1137
|
if len(content_parts) == 1:
|
|
1495
1138
|
# Return the original content with no tool calls processed, and the content itself as before_separator
|
|
1496
|
-
return content, result_messages, False, content
|
|
1139
|
+
return content, result_messages, False, content, tool_names
|
|
1497
1140
|
|
|
1498
1141
|
# Take everything before the last separator (including intermediate separators)
|
|
1499
1142
|
content_before_separator = separator_marker.join(content_parts[:-1])
|
|
@@ -1683,6 +1326,8 @@ class NavigatorCoder(Coder):
|
|
|
1683
1326
|
else:
|
|
1684
1327
|
raise ValueError("Tool name must be an identifier or a string literal")
|
|
1685
1328
|
|
|
1329
|
+
tool_names.append(tool_name)
|
|
1330
|
+
|
|
1686
1331
|
# Extract keyword arguments
|
|
1687
1332
|
for keyword in call_node.keywords:
|
|
1688
1333
|
key = keyword.arg
|
|
@@ -1764,20 +1409,13 @@ class NavigatorCoder(Coder):
|
|
|
1764
1409
|
# Normalize tool name for case-insensitive matching
|
|
1765
1410
|
norm_tool_name = tool_name.lower()
|
|
1766
1411
|
|
|
1767
|
-
if norm_tool_name == "
|
|
1768
|
-
pattern = params.get("pattern")
|
|
1769
|
-
if pattern is not None:
|
|
1770
|
-
# Call the imported function
|
|
1771
|
-
result_message = execute_view_files_at_glob(self, pattern)
|
|
1772
|
-
else:
|
|
1773
|
-
result_message = "Error: Missing 'pattern' parameter for ViewFilesAtGlob"
|
|
1774
|
-
elif norm_tool_name == "viewfilesmatching":
|
|
1412
|
+
if norm_tool_name == "viewfilesmatching":
|
|
1775
1413
|
pattern = params.get("pattern")
|
|
1776
1414
|
file_pattern = params.get("file_pattern") # Optional
|
|
1777
1415
|
regex = params.get("regex", False) # Default to False if not provided
|
|
1778
1416
|
if pattern is not None:
|
|
1779
1417
|
result_message = execute_view_files_matching(
|
|
1780
|
-
self, pattern, file_pattern, regex
|
|
1418
|
+
self, pattern=pattern, file_pattern=file_pattern, regex=regex
|
|
1781
1419
|
)
|
|
1782
1420
|
else:
|
|
1783
1421
|
result_message = "Error: Missing 'pattern' parameter for ViewFilesMatching"
|
|
@@ -1823,13 +1461,13 @@ class NavigatorCoder(Coder):
|
|
|
1823
1461
|
elif norm_tool_name == "command":
|
|
1824
1462
|
command_string = params.get("command_string")
|
|
1825
1463
|
if command_string is not None:
|
|
1826
|
-
result_message = _execute_command(self, command_string)
|
|
1464
|
+
result_message = await _execute_command(self, command_string)
|
|
1827
1465
|
else:
|
|
1828
1466
|
result_message = "Error: Missing 'command_string' parameter for Command"
|
|
1829
1467
|
elif norm_tool_name == "commandinteractive":
|
|
1830
1468
|
command_string = params.get("command_string")
|
|
1831
1469
|
if command_string is not None:
|
|
1832
|
-
result_message = _execute_command_interactive(self, command_string)
|
|
1470
|
+
result_message = await _execute_command_interactive(self, command_string)
|
|
1833
1471
|
else:
|
|
1834
1472
|
result_message = (
|
|
1835
1473
|
"Error: Missing 'command_string' parameter for CommandInteractive"
|
|
@@ -1848,10 +1486,8 @@ class NavigatorCoder(Coder):
|
|
|
1848
1486
|
context_after = params.get("context_after", 5)
|
|
1849
1487
|
|
|
1850
1488
|
if pattern is not None:
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
result_message = _execute_grep(
|
|
1489
|
+
result_message = await asyncio.to_thread(
|
|
1490
|
+
_execute_grep,
|
|
1855
1491
|
self,
|
|
1856
1492
|
pattern,
|
|
1857
1493
|
file_pattern,
|
|
@@ -1942,6 +1578,7 @@ class NavigatorCoder(Coder):
|
|
|
1942
1578
|
auto_indent,
|
|
1943
1579
|
use_regex,
|
|
1944
1580
|
)
|
|
1581
|
+
|
|
1945
1582
|
else:
|
|
1946
1583
|
result_message = (
|
|
1947
1584
|
"Error: Missing required parameters for InsertBlock (file_path,"
|
|
@@ -2139,6 +1776,21 @@ class NavigatorCoder(Coder):
|
|
|
2139
1776
|
" and either pattern or line_number)"
|
|
2140
1777
|
)
|
|
2141
1778
|
|
|
1779
|
+
elif norm_tool_name == "updatetodolist":
|
|
1780
|
+
content = params.get("content")
|
|
1781
|
+
append = params.get("append", False)
|
|
1782
|
+
change_id = params.get("change_id")
|
|
1783
|
+
dry_run = params.get("dry_run", False)
|
|
1784
|
+
|
|
1785
|
+
if content is not None:
|
|
1786
|
+
result_message = _execute_update_todo_list(
|
|
1787
|
+
self, content, append, change_id, dry_run
|
|
1788
|
+
)
|
|
1789
|
+
else:
|
|
1790
|
+
result_message = (
|
|
1791
|
+
"Error: Missing required 'content' parameter for UpdateTodoList"
|
|
1792
|
+
)
|
|
1793
|
+
|
|
2142
1794
|
else:
|
|
2143
1795
|
result_message = f"Error: Unknown tool name '{tool_name}'"
|
|
2144
1796
|
if self.mcp_tools:
|
|
@@ -2150,7 +1802,7 @@ class NavigatorCoder(Coder):
|
|
|
2150
1802
|
(s for s in self.mcp_servers if s.name == server_name), None
|
|
2151
1803
|
)
|
|
2152
1804
|
if server:
|
|
2153
|
-
result_message = self._execute_mcp_tool(
|
|
1805
|
+
result_message = await self._execute_mcp_tool(
|
|
2154
1806
|
server, tool_name, params
|
|
2155
1807
|
)
|
|
2156
1808
|
else:
|
|
@@ -2176,12 +1828,124 @@ class NavigatorCoder(Coder):
|
|
|
2176
1828
|
# Return the content with tool calls removed
|
|
2177
1829
|
modified_content = processed_content
|
|
2178
1830
|
|
|
2179
|
-
|
|
2180
|
-
|
|
1831
|
+
return (
|
|
1832
|
+
modified_content,
|
|
1833
|
+
result_messages,
|
|
1834
|
+
tool_calls_found,
|
|
1835
|
+
content_before_separator,
|
|
1836
|
+
tool_names,
|
|
1837
|
+
)
|
|
1838
|
+
|
|
1839
|
+
def _get_repetitive_tools(self):
|
|
1840
|
+
"""
|
|
1841
|
+
Identifies repetitive tool usage patterns from a flat list of tool calls.
|
|
1842
|
+
|
|
1843
|
+
This method checks for the following patterns in order:
|
|
1844
|
+
1. If the last tool used was a write tool, it assumes progress and returns no repetitive tools.
|
|
1845
|
+
2. It checks for any read tool that has been used 2 or more times in the history.
|
|
1846
|
+
3. If no tools are repeated, but all tools in the history are read tools,
|
|
1847
|
+
it flags all of them as potentially repetitive.
|
|
1848
|
+
|
|
1849
|
+
It avoids flagging repetition if a "write" tool was used recently,
|
|
1850
|
+
as that suggests progress is being made.
|
|
1851
|
+
"""
|
|
1852
|
+
history_len = len(self.tool_usage_history)
|
|
1853
|
+
|
|
1854
|
+
# Not enough history to detect a pattern
|
|
1855
|
+
if history_len < 2:
|
|
1856
|
+
return set()
|
|
1857
|
+
|
|
1858
|
+
# If the last tool was a write tool, we're likely making progress.
|
|
1859
|
+
if isinstance(self.tool_usage_history[-1], str):
|
|
1860
|
+
last_tool_lower = self.tool_usage_history[-1].lower()
|
|
1861
|
+
|
|
1862
|
+
if last_tool_lower in self.write_tools:
|
|
1863
|
+
self.tool_usage_history = []
|
|
1864
|
+
return set()
|
|
1865
|
+
|
|
1866
|
+
# If all tools in history are read tools, return all of them
|
|
1867
|
+
if all(tool.lower() in self.read_tools for tool in self.tool_usage_history):
|
|
1868
|
+
return set(tool for tool in self.tool_usage_history)
|
|
1869
|
+
|
|
1870
|
+
# Check for any read tool used more than once
|
|
1871
|
+
tool_counts = Counter(tool for tool in self.tool_usage_history)
|
|
1872
|
+
repetitive_tools = {
|
|
1873
|
+
tool
|
|
1874
|
+
for tool, count in tool_counts.items()
|
|
1875
|
+
if count >= 2 and tool.lower() in self.read_tools
|
|
1876
|
+
}
|
|
1877
|
+
|
|
1878
|
+
if repetitive_tools:
|
|
1879
|
+
return repetitive_tools
|
|
1880
|
+
|
|
1881
|
+
return set()
|
|
1882
|
+
|
|
1883
|
+
def _generate_tool_context(self, repetitive_tools):
|
|
1884
|
+
"""
|
|
1885
|
+
Generate a context message for the LLM about recent tool usage.
|
|
1886
|
+
"""
|
|
1887
|
+
if not self.tool_usage_history:
|
|
1888
|
+
return ""
|
|
1889
|
+
|
|
1890
|
+
context_parts = ['<context name="tool_usage_history">']
|
|
1891
|
+
|
|
1892
|
+
# Add turn and tool call statistics
|
|
1893
|
+
context_parts.append("## Turn and Tool Call Statistics")
|
|
1894
|
+
context_parts.append(f"- Current turn: {self.num_reflections + 1}")
|
|
1895
|
+
context_parts.append(f"- Tool calls this turn: {self.tool_call_count}")
|
|
1896
|
+
context_parts.append(f"- Total tool calls in session: {self.num_tool_calls}")
|
|
1897
|
+
context_parts.append("\n\n")
|
|
1898
|
+
|
|
1899
|
+
# Add recent tool usage history
|
|
1900
|
+
context_parts.append("## Recent Tool Usage History")
|
|
1901
|
+
if len(self.tool_usage_history) > 10:
|
|
1902
|
+
recent_history = self.tool_usage_history[-10:]
|
|
1903
|
+
context_parts.append("(Showing last 10 tools)")
|
|
1904
|
+
else:
|
|
1905
|
+
recent_history = self.tool_usage_history
|
|
1906
|
+
|
|
1907
|
+
for i, tool in enumerate(recent_history, 1):
|
|
1908
|
+
context_parts.append(f"{i}. {tool}")
|
|
1909
|
+
context_parts.append("\n\n")
|
|
1910
|
+
|
|
1911
|
+
if repetitive_tools:
|
|
1912
|
+
context_parts.append(
|
|
1913
|
+
"**Instruction:**\nYou have used the following tool(s) repeatedly:"
|
|
1914
|
+
)
|
|
2181
1915
|
|
|
2182
|
-
|
|
1916
|
+
context_parts.append("### DO NOT USE THE FOLLOWING TOOLS/FUNCTIONS")
|
|
2183
1917
|
|
|
2184
|
-
|
|
1918
|
+
for tool in repetitive_tools:
|
|
1919
|
+
context_parts.append(f"- `{tool}`")
|
|
1920
|
+
context_parts.append(
|
|
1921
|
+
"Your exploration appears to be stuck in a loop. Please try a different approach:"
|
|
1922
|
+
)
|
|
1923
|
+
context_parts.append("\n")
|
|
1924
|
+
context_parts.append("**Suggestions for alternative approaches:**")
|
|
1925
|
+
context_parts.append(
|
|
1926
|
+
"- If you've been searching for files, try working with the files already in"
|
|
1927
|
+
" context"
|
|
1928
|
+
)
|
|
1929
|
+
context_parts.append(
|
|
1930
|
+
"- If you've been viewing files, try making actual edits to move forward"
|
|
1931
|
+
)
|
|
1932
|
+
context_parts.append("- Consider using different tools that you haven't used recently")
|
|
1933
|
+
context_parts.append(
|
|
1934
|
+
"- Focus on making concrete progress rather than gathering more information"
|
|
1935
|
+
)
|
|
1936
|
+
context_parts.append(
|
|
1937
|
+
"- Use the files you've already discovered to implement the requested changes"
|
|
1938
|
+
)
|
|
1939
|
+
context_parts.append("\n")
|
|
1940
|
+
context_parts.append(
|
|
1941
|
+
"You most likely have enough context for a subset of the necessary changes."
|
|
1942
|
+
)
|
|
1943
|
+
context_parts.append("Please prioritize file editing over further exploration.")
|
|
1944
|
+
|
|
1945
|
+
context_parts.append("</context>")
|
|
1946
|
+
return "\n".join(context_parts)
|
|
1947
|
+
|
|
1948
|
+
async def _apply_edits_from_response(self):
|
|
2185
1949
|
"""
|
|
2186
1950
|
Parses and applies SEARCH/REPLACE edits found in self.partial_response_content.
|
|
2187
1951
|
Returns a set of relative file paths that were successfully edited.
|
|
@@ -2213,13 +1977,13 @@ class NavigatorCoder(Coder):
|
|
|
2213
1977
|
allowed = seen_paths[path]
|
|
2214
1978
|
else:
|
|
2215
1979
|
# Use the base Coder's permission check method
|
|
2216
|
-
allowed = self.allowed_to_edit(path)
|
|
1980
|
+
allowed = await self.allowed_to_edit(path)
|
|
2217
1981
|
seen_paths[path] = allowed
|
|
2218
1982
|
if allowed:
|
|
2219
1983
|
prepared_edits.append(edit)
|
|
2220
1984
|
|
|
2221
1985
|
# Commit any dirty files identified by allowed_to_edit
|
|
2222
|
-
self.dirty_commit()
|
|
1986
|
+
await self.dirty_commit()
|
|
2223
1987
|
self.need_commit_before_edits = set() # Clear after commit
|
|
2224
1988
|
|
|
2225
1989
|
# 3. Apply edits (logic adapted from EditBlockCoder.apply_edits)
|
|
@@ -2318,20 +2082,20 @@ Just reply with fixed versions of the {blocks} above that failed to match.
|
|
|
2318
2082
|
lint_errors = self.lint_edited(edited_files)
|
|
2319
2083
|
self.auto_commit(edited_files, context="Ran the linter")
|
|
2320
2084
|
if lint_errors and not self.reflected_message: # Reflect only if no edit errors
|
|
2321
|
-
ok = self.io.confirm_ask("Attempt to fix lint errors?")
|
|
2085
|
+
ok = await self.io.confirm_ask("Attempt to fix lint errors?")
|
|
2322
2086
|
if ok:
|
|
2323
2087
|
self.reflected_message = lint_errors
|
|
2324
2088
|
|
|
2325
|
-
shared_output = self.run_shell_commands()
|
|
2089
|
+
shared_output = await self.run_shell_commands()
|
|
2326
2090
|
if shared_output:
|
|
2327
2091
|
# Add shell output as a new user message? Or just display?
|
|
2328
2092
|
# Let's just display for now to avoid complex history manipulation
|
|
2329
2093
|
self.io.tool_output("Shell command output:\n" + shared_output)
|
|
2330
2094
|
|
|
2331
2095
|
if self.auto_test and not self.reflected_message: # Reflect only if no prior errors
|
|
2332
|
-
test_errors = self.commands.cmd_test(self.test_cmd)
|
|
2096
|
+
test_errors = await self.commands.cmd_test(self.test_cmd)
|
|
2333
2097
|
if test_errors:
|
|
2334
|
-
ok = self.io.confirm_ask("Attempt to fix test errors?")
|
|
2098
|
+
ok = await self.io.confirm_ask("Attempt to fix test errors?")
|
|
2335
2099
|
if ok:
|
|
2336
2100
|
self.reflected_message = test_errors
|
|
2337
2101
|
|
|
@@ -2352,7 +2116,7 @@ Just reply with fixed versions of the {blocks} above that failed to match.
|
|
|
2352
2116
|
except Exception as err:
|
|
2353
2117
|
self.io.tool_error("Exception while applying edits:")
|
|
2354
2118
|
self.io.tool_error(str(err), strip=False)
|
|
2355
|
-
traceback.
|
|
2119
|
+
self.io.tool_error(traceback.format_exc())
|
|
2356
2120
|
self.reflected_message = f"Exception while applying edits: {str(err)}"
|
|
2357
2121
|
|
|
2358
2122
|
return edited_files
|
|
@@ -2363,7 +2127,7 @@ Just reply with fixed versions of the {blocks} above that failed to match.
|
|
|
2363
2127
|
|
|
2364
2128
|
Parameters:
|
|
2365
2129
|
- file_path: Path to the file to add
|
|
2366
|
-
- explicit: Whether this was an explicit view command (vs. implicit through
|
|
2130
|
+
- explicit: Whether this was an explicit view command (vs. implicit through ViewFilesMatching)
|
|
2367
2131
|
"""
|
|
2368
2132
|
# Check if file exists
|
|
2369
2133
|
abs_path = self.abs_root_path(file_path)
|
|
@@ -2437,7 +2201,7 @@ Just reply with fixed versions of the {blocks} above that failed to match.
|
|
|
2437
2201
|
# Do nothing here for implicit mentions.
|
|
2438
2202
|
pass
|
|
2439
2203
|
|
|
2440
|
-
def check_for_file_mentions(self, content):
|
|
2204
|
+
async def check_for_file_mentions(self, content):
|
|
2441
2205
|
"""
|
|
2442
2206
|
Override parent's method to use our own file processing logic.
|
|
2443
2207
|
|
|
@@ -2448,13 +2212,13 @@ Just reply with fixed versions of the {blocks} above that failed to match.
|
|
|
2448
2212
|
# Do nothing - disable implicit file adds in navigator mode.
|
|
2449
2213
|
pass
|
|
2450
2214
|
|
|
2451
|
-
def preproc_user_input(self, inp):
|
|
2215
|
+
async def preproc_user_input(self, inp):
|
|
2452
2216
|
"""
|
|
2453
2217
|
Override parent's method to wrap user input in a context block.
|
|
2454
2218
|
This clearly delineates user input from other sections in the context window.
|
|
2455
2219
|
"""
|
|
2456
2220
|
# First apply the parent's preprocessing
|
|
2457
|
-
inp = super().preproc_user_input(inp)
|
|
2221
|
+
inp = await super().preproc_user_input(inp)
|
|
2458
2222
|
|
|
2459
2223
|
# If we still have input after preprocessing, wrap it in a context block
|
|
2460
2224
|
if inp and not inp.startswith('<context name="user_input">'):
|
|
@@ -2566,6 +2330,44 @@ Just reply with fixed versions of the {blocks} above that failed to match.
|
|
|
2566
2330
|
self.io.tool_error(f"Error generating directory structure: {str(e)}")
|
|
2567
2331
|
return None
|
|
2568
2332
|
|
|
2333
|
+
def get_todo_list(self):
|
|
2334
|
+
"""
|
|
2335
|
+
Generate a todo list context block from the .aider.todo.txt file.
|
|
2336
|
+
Returns formatted string with the current todo list or None if empty/not present.
|
|
2337
|
+
"""
|
|
2338
|
+
|
|
2339
|
+
try:
|
|
2340
|
+
# Define the todo file path
|
|
2341
|
+
todo_file_path = ".aider.todo.txt"
|
|
2342
|
+
abs_path = self.abs_root_path(todo_file_path)
|
|
2343
|
+
|
|
2344
|
+
# Check if file exists
|
|
2345
|
+
import os
|
|
2346
|
+
|
|
2347
|
+
if not os.path.isfile(abs_path):
|
|
2348
|
+
return (
|
|
2349
|
+
'<context name="todo_list">\n'
|
|
2350
|
+
"Todo list does not exist. Please update it."
|
|
2351
|
+
"</context>"
|
|
2352
|
+
)
|
|
2353
|
+
|
|
2354
|
+
# Read todo list content
|
|
2355
|
+
content = self.io.read_text(abs_path)
|
|
2356
|
+
if content is None or not content.strip():
|
|
2357
|
+
return None
|
|
2358
|
+
|
|
2359
|
+
# Format the todo list context block
|
|
2360
|
+
result = '<context name="todo_list">\n'
|
|
2361
|
+
result += "## Current Todo List\n\n"
|
|
2362
|
+
result += "Below is the current todo list managed via `UpdateTodoList` tool:\n\n"
|
|
2363
|
+
result += f"```\n{content}\n```\n"
|
|
2364
|
+
result += "</context>"
|
|
2365
|
+
|
|
2366
|
+
return result
|
|
2367
|
+
except Exception as e:
|
|
2368
|
+
self.io.tool_error(f"Error generating todo list context: {str(e)}")
|
|
2369
|
+
return None
|
|
2370
|
+
|
|
2569
2371
|
def get_git_status(self):
|
|
2570
2372
|
"""
|
|
2571
2373
|
Generate a git status context block for repository information.
|