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.
- aider/__init__.py +1 -1
- aider/_version.py +2 -2
- aider/args.py +63 -43
- aider/coders/agent_coder.py +331 -79
- aider/coders/agent_prompts.py +3 -15
- aider/coders/architect_coder.py +21 -5
- aider/coders/base_coder.py +661 -413
- aider/coders/base_prompts.py +6 -3
- aider/coders/chat_chunks.py +39 -17
- aider/commands.py +79 -15
- aider/diffs.py +10 -9
- aider/exceptions.py +1 -1
- aider/helpers/coroutines.py +8 -0
- aider/helpers/requests.py +45 -0
- aider/history.py +5 -0
- aider/io.py +179 -25
- aider/main.py +86 -35
- aider/models.py +16 -8
- aider/queries/tree-sitter-language-pack/c-tags.scm +3 -0
- aider/queries/tree-sitter-language-pack/clojure-tags.scm +5 -0
- aider/queries/tree-sitter-language-pack/commonlisp-tags.scm +5 -0
- aider/queries/tree-sitter-language-pack/cpp-tags.scm +3 -0
- aider/queries/tree-sitter-language-pack/csharp-tags.scm +6 -0
- aider/queries/tree-sitter-language-pack/dart-tags.scm +5 -0
- aider/queries/tree-sitter-language-pack/elixir-tags.scm +5 -0
- aider/queries/tree-sitter-language-pack/elm-tags.scm +3 -0
- aider/queries/tree-sitter-language-pack/go-tags.scm +7 -0
- aider/queries/tree-sitter-language-pack/java-tags.scm +6 -0
- aider/queries/tree-sitter-language-pack/javascript-tags.scm +8 -0
- aider/queries/tree-sitter-language-pack/lua-tags.scm +5 -0
- aider/queries/tree-sitter-language-pack/ocaml_interface-tags.scm +3 -0
- aider/queries/tree-sitter-language-pack/python-tags.scm +10 -0
- aider/queries/tree-sitter-language-pack/r-tags.scm +6 -0
- aider/queries/tree-sitter-language-pack/ruby-tags.scm +5 -0
- aider/queries/tree-sitter-language-pack/rust-tags.scm +3 -0
- aider/queries/tree-sitter-language-pack/solidity-tags.scm +1 -1
- aider/queries/tree-sitter-language-pack/swift-tags.scm +4 -1
- aider/queries/tree-sitter-languages/c-tags.scm +3 -0
- aider/queries/tree-sitter-languages/c_sharp-tags.scm +6 -0
- aider/queries/tree-sitter-languages/cpp-tags.scm +3 -0
- aider/queries/tree-sitter-languages/dart-tags.scm +2 -1
- aider/queries/tree-sitter-languages/elixir-tags.scm +5 -0
- aider/queries/tree-sitter-languages/elm-tags.scm +3 -0
- aider/queries/tree-sitter-languages/fortran-tags.scm +3 -0
- aider/queries/tree-sitter-languages/go-tags.scm +6 -0
- aider/queries/tree-sitter-languages/haskell-tags.scm +2 -0
- aider/queries/tree-sitter-languages/java-tags.scm +6 -0
- aider/queries/tree-sitter-languages/javascript-tags.scm +8 -0
- aider/queries/tree-sitter-languages/julia-tags.scm +2 -2
- aider/queries/tree-sitter-languages/kotlin-tags.scm +3 -0
- aider/queries/tree-sitter-languages/ocaml_interface-tags.scm +6 -0
- aider/queries/tree-sitter-languages/php-tags.scm +6 -0
- aider/queries/tree-sitter-languages/python-tags.scm +10 -0
- aider/queries/tree-sitter-languages/ruby-tags.scm +5 -0
- aider/queries/tree-sitter-languages/rust-tags.scm +3 -0
- aider/queries/tree-sitter-languages/scala-tags.scm +2 -3
- aider/queries/tree-sitter-languages/typescript-tags.scm +3 -0
- aider/queries/tree-sitter-languages/zig-tags.scm +20 -3
- aider/repomap.py +71 -11
- aider/resources/model-metadata.json +27335 -635
- aider/resources/model-settings.yml +190 -0
- aider/scrape.py +2 -0
- aider/tools/__init__.py +2 -0
- aider/tools/command.py +84 -94
- aider/tools/command_interactive.py +95 -110
- aider/tools/delete_block.py +131 -159
- aider/tools/delete_line.py +97 -132
- aider/tools/delete_lines.py +120 -160
- aider/tools/extract_lines.py +288 -312
- aider/tools/finished.py +30 -43
- aider/tools/git_branch.py +107 -109
- aider/tools/git_diff.py +44 -56
- aider/tools/git_log.py +39 -53
- aider/tools/git_remote.py +37 -51
- aider/tools/git_show.py +33 -47
- aider/tools/git_status.py +30 -44
- aider/tools/grep.py +214 -242
- aider/tools/indent_lines.py +175 -201
- aider/tools/insert_block.py +220 -253
- aider/tools/list_changes.py +65 -80
- aider/tools/ls.py +64 -80
- aider/tools/make_editable.py +57 -73
- aider/tools/make_readonly.py +50 -66
- aider/tools/remove.py +64 -80
- aider/tools/replace_all.py +96 -109
- aider/tools/replace_line.py +118 -156
- aider/tools/replace_lines.py +160 -197
- aider/tools/replace_text.py +159 -160
- aider/tools/show_numbered_context.py +115 -141
- aider/tools/thinking.py +52 -0
- aider/tools/undo_change.py +78 -91
- aider/tools/update_todo_list.py +130 -138
- aider/tools/utils/base_tool.py +64 -0
- aider/tools/utils/output.py +118 -0
- aider/tools/view.py +38 -54
- aider/tools/view_files_matching.py +131 -134
- aider/tools/view_files_with_symbol.py +108 -120
- aider/urls.py +1 -1
- aider/versioncheck.py +4 -3
- aider/website/docs/config/adv-model-settings.md +237 -0
- aider/website/docs/config/agent-mode.md +36 -3
- aider/website/docs/config/model-aliases.md +2 -1
- aider/website/docs/faq.md +6 -11
- aider/website/docs/languages.md +2 -2
- aider/website/docs/more/infinite-output.md +27 -0
- {aider_ce-0.88.20.dist-info → aider_ce-0.88.38.dist-info}/METADATA +112 -70
- {aider_ce-0.88.20.dist-info → aider_ce-0.88.38.dist-info}/RECORD +112 -107
- aider_ce-0.88.38.dist-info/entry_points.txt +6 -0
- aider_ce-0.88.20.dist-info/entry_points.txt +0 -2
- /aider/tools/{tool_utils.py → utils/helpers.py} +0 -0
- {aider_ce-0.88.20.dist-info → aider_ce-0.88.38.dist-info}/WHEEL +0 -0
- {aider_ce-0.88.20.dist-info → aider_ce-0.88.38.dist-info}/licenses/LICENSE.txt +0 -0
- {aider_ce-0.88.20.dist-info → aider_ce-0.88.38.dist-info}/top_level.txt +0 -0
aider/coders/agent_coder.py
CHANGED
|
@@ -20,6 +20,13 @@ from aider import urls, utils
|
|
|
20
20
|
|
|
21
21
|
# Import the change tracker
|
|
22
22
|
from aider.change_tracker import ChangeTracker
|
|
23
|
+
|
|
24
|
+
# Import similarity functions for tool usage analysis
|
|
25
|
+
from aider.helpers.similarity import (
|
|
26
|
+
cosine_similarity,
|
|
27
|
+
create_bigram_vector,
|
|
28
|
+
normalize_vector,
|
|
29
|
+
)
|
|
23
30
|
from aider.mcp.server import LocalServer
|
|
24
31
|
from aider.repo import ANY_GIT_ERROR
|
|
25
32
|
|
|
@@ -52,6 +59,7 @@ from aider.tools import (
|
|
|
52
59
|
replace_lines,
|
|
53
60
|
replace_text,
|
|
54
61
|
show_numbered_context,
|
|
62
|
+
thinking,
|
|
55
63
|
undo_change,
|
|
56
64
|
update_todo_list,
|
|
57
65
|
view,
|
|
@@ -78,8 +86,15 @@ class AgentCoder(Coder):
|
|
|
78
86
|
self.recently_removed = {}
|
|
79
87
|
|
|
80
88
|
# Tool usage history
|
|
81
|
-
self.tool_usage_history = []
|
|
89
|
+
self.tool_usage_history = [] # Stores lists of tools used in each round
|
|
82
90
|
self.tool_usage_retries = 10
|
|
91
|
+
self.last_round_tools = [] # Tools used in the current round
|
|
92
|
+
|
|
93
|
+
# Similarity tracking for tool usage
|
|
94
|
+
self.tool_call_vectors = [] # Store vectors for individual tool calls
|
|
95
|
+
self.tool_similarity_threshold = 0.99 # High threshold for exact matches
|
|
96
|
+
self.max_tool_vector_history = 10 # Keep history of 10 rounds
|
|
97
|
+
|
|
83
98
|
self.read_tools = {
|
|
84
99
|
"viewfilesatglob",
|
|
85
100
|
"viewfilesmatching",
|
|
@@ -101,7 +116,7 @@ class AgentCoder(Coder):
|
|
|
101
116
|
}
|
|
102
117
|
|
|
103
118
|
# Configuration parameters
|
|
104
|
-
self.max_tool_calls =
|
|
119
|
+
self.max_tool_calls = 10000 # Maximum number of tool calls per response
|
|
105
120
|
|
|
106
121
|
# Context management parameters
|
|
107
122
|
# Will be overridden by agent_config if provided
|
|
@@ -117,7 +132,7 @@ class AgentCoder(Coder):
|
|
|
117
132
|
|
|
118
133
|
# Initialize tool registry
|
|
119
134
|
self.args = kwargs.get("args")
|
|
120
|
-
self.
|
|
135
|
+
self.tool_registry = self._build_tool_registry()
|
|
121
136
|
|
|
122
137
|
# Track files added during current exploration
|
|
123
138
|
self.files_added_in_exploration = set()
|
|
@@ -132,14 +147,20 @@ class AgentCoder(Coder):
|
|
|
132
147
|
# Enable enhanced context blocks by default
|
|
133
148
|
self.use_enhanced_context = True
|
|
134
149
|
|
|
150
|
+
# Caching efficiency attributes
|
|
151
|
+
self._last_edited_file = None
|
|
152
|
+
self._cur_message_divider = None
|
|
153
|
+
|
|
135
154
|
# Initialize empty token tracking dictionary and cache structures
|
|
136
155
|
# but don't populate yet to avoid startup delay
|
|
156
|
+
self.allowed_context_blocks = set()
|
|
137
157
|
self.context_block_tokens = {}
|
|
138
158
|
self.context_blocks_cache = {}
|
|
139
159
|
self.tokens_calculated = False
|
|
140
160
|
|
|
141
161
|
self.skip_cli_confirmations = False
|
|
142
162
|
|
|
163
|
+
self.agent_finished = False
|
|
143
164
|
self._get_agent_config()
|
|
144
165
|
super().__init__(*args, **kwargs)
|
|
145
166
|
|
|
@@ -181,6 +202,7 @@ class AgentCoder(Coder):
|
|
|
181
202
|
replace_lines,
|
|
182
203
|
replace_text,
|
|
183
204
|
show_numbered_context,
|
|
205
|
+
thinking,
|
|
184
206
|
undo_change,
|
|
185
207
|
update_todo_list,
|
|
186
208
|
view,
|
|
@@ -200,8 +222,9 @@ class AgentCoder(Coder):
|
|
|
200
222
|
# Always include essential tools regardless of includelist/excludelist
|
|
201
223
|
essential_tools = {"makeeditable", "replacetext", "view", "finished"}
|
|
202
224
|
for module in tool_modules:
|
|
203
|
-
if hasattr(module, "
|
|
204
|
-
|
|
225
|
+
if hasattr(module, "Tool"):
|
|
226
|
+
tool_class = module.Tool
|
|
227
|
+
tool_name = tool_class.NORM_NAME
|
|
205
228
|
|
|
206
229
|
# Check if tool should be included based on configuration
|
|
207
230
|
should_include = True
|
|
@@ -219,7 +242,7 @@ class AgentCoder(Coder):
|
|
|
219
242
|
should_include = False
|
|
220
243
|
|
|
221
244
|
if should_include:
|
|
222
|
-
registry[tool_name] =
|
|
245
|
+
registry[tool_name] = tool_class
|
|
223
246
|
|
|
224
247
|
return registry
|
|
225
248
|
|
|
@@ -253,6 +276,25 @@ class AgentCoder(Coder):
|
|
|
253
276
|
if "tools_excludelist" not in config:
|
|
254
277
|
config["tools_excludelist"] = []
|
|
255
278
|
|
|
279
|
+
if "include_context_blocks" in config:
|
|
280
|
+
self.allowed_context_blocks = set(config["context_blocks"])
|
|
281
|
+
else:
|
|
282
|
+
self.allowed_context_blocks = {
|
|
283
|
+
"context_summary",
|
|
284
|
+
"directory_structure",
|
|
285
|
+
"environment_info",
|
|
286
|
+
"git_status",
|
|
287
|
+
"symbol_outline",
|
|
288
|
+
"todo_list",
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if "exclude_context_blocks" in config:
|
|
292
|
+
for context_block in config["exclude_context_blocks"]:
|
|
293
|
+
try:
|
|
294
|
+
self.allowed_context_blocks.remove(context_block)
|
|
295
|
+
except KeyError:
|
|
296
|
+
pass
|
|
297
|
+
|
|
256
298
|
# Apply configuration to instance
|
|
257
299
|
self.large_file_token_threshold = config["large_file_token_threshold"]
|
|
258
300
|
self.skip_cli_confirmations = config.get(
|
|
@@ -266,9 +308,9 @@ class AgentCoder(Coder):
|
|
|
266
308
|
schemas = []
|
|
267
309
|
|
|
268
310
|
# Get schemas from the tool registry
|
|
269
|
-
for tool_module in self.
|
|
270
|
-
if hasattr(tool_module, "
|
|
271
|
-
schemas.append(tool_module.
|
|
311
|
+
for tool_module in self.tool_registry.values():
|
|
312
|
+
if hasattr(tool_module, "SCHEMA"):
|
|
313
|
+
schemas.append(tool_module.SCHEMA)
|
|
272
314
|
|
|
273
315
|
return schemas
|
|
274
316
|
|
|
@@ -323,8 +365,8 @@ class AgentCoder(Coder):
|
|
|
323
365
|
tasks = []
|
|
324
366
|
|
|
325
367
|
# Use the tool registry for execution
|
|
326
|
-
if norm_tool_name in self.
|
|
327
|
-
tool_module = self.
|
|
368
|
+
if norm_tool_name in self.tool_registry:
|
|
369
|
+
tool_module = self.tool_registry[norm_tool_name]
|
|
328
370
|
for params in parsed_args_list:
|
|
329
371
|
# Use the process_response function from the tool module
|
|
330
372
|
result = tool_module.process_response(self, params)
|
|
@@ -464,11 +506,12 @@ class AgentCoder(Coder):
|
|
|
464
506
|
]
|
|
465
507
|
|
|
466
508
|
for block_type in block_types:
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
509
|
+
if block_type in self.allowed_context_blocks:
|
|
510
|
+
block_content = self._generate_context_block(block_type)
|
|
511
|
+
if block_content:
|
|
512
|
+
self.context_block_tokens[block_type] = self.main_model.token_count(
|
|
513
|
+
block_content
|
|
514
|
+
)
|
|
472
515
|
|
|
473
516
|
# Mark as calculated
|
|
474
517
|
self.tokens_calculated = True
|
|
@@ -651,7 +694,19 @@ class AgentCoder(Coder):
|
|
|
651
694
|
if self.gpt_prompts.system_reminder:
|
|
652
695
|
main_sys += "\n" + self.fmt_system_prompt(self.gpt_prompts.system_reminder)
|
|
653
696
|
|
|
654
|
-
chunks = ChatChunks(
|
|
697
|
+
chunks = ChatChunks(
|
|
698
|
+
chunk_ordering=[
|
|
699
|
+
"system",
|
|
700
|
+
"examples",
|
|
701
|
+
"readonly_files",
|
|
702
|
+
"repo",
|
|
703
|
+
"chat_files",
|
|
704
|
+
"done",
|
|
705
|
+
"edit_files",
|
|
706
|
+
"cur",
|
|
707
|
+
"reminder",
|
|
708
|
+
]
|
|
709
|
+
)
|
|
655
710
|
|
|
656
711
|
if self.main_model.use_system_prompt:
|
|
657
712
|
chunks.system = [
|
|
@@ -666,11 +721,40 @@ class AgentCoder(Coder):
|
|
|
666
721
|
chunks.examples = example_messages
|
|
667
722
|
|
|
668
723
|
self.summarize_end()
|
|
669
|
-
|
|
724
|
+
cur_messages_list = list(self.cur_messages)
|
|
725
|
+
cur_messages_pre = []
|
|
726
|
+
cur_messages_post = cur_messages_list
|
|
727
|
+
chunks.readonly_files = self.get_readonly_files_messages()
|
|
728
|
+
|
|
729
|
+
# Handle the dictionary structure from get_chat_files_messages()
|
|
730
|
+
chat_files_result = self.get_chat_files_messages()
|
|
731
|
+
chunks.chat_files = chat_files_result.get("chat_files", [])
|
|
732
|
+
chunks.edit_files = chat_files_result.get("edit_files", [])
|
|
733
|
+
edit_file_names = chat_files_result.get("edit_file_names", set())
|
|
734
|
+
|
|
735
|
+
# Update edit file tracking for caching efficiency
|
|
736
|
+
divider = self._update_edit_file_tracking(edit_file_names)
|
|
737
|
+
if divider is not None:
|
|
738
|
+
# Split cur_messages using the divider
|
|
739
|
+
if divider > 0 and divider < len(cur_messages_list):
|
|
740
|
+
cur_messages_pre = cur_messages_list[:divider]
|
|
741
|
+
cur_messages_post = cur_messages_list[divider:]
|
|
670
742
|
|
|
671
743
|
chunks.repo = self.get_repo_messages()
|
|
672
|
-
chunks.
|
|
673
|
-
|
|
744
|
+
chunks.done = list(self.done_messages) + cur_messages_pre
|
|
745
|
+
|
|
746
|
+
# Add reminder if needed
|
|
747
|
+
if self.gpt_prompts.system_reminder:
|
|
748
|
+
reminder_message = [
|
|
749
|
+
dict(
|
|
750
|
+
role="system", content=self.fmt_system_prompt(self.gpt_prompts.system_reminder)
|
|
751
|
+
),
|
|
752
|
+
]
|
|
753
|
+
else:
|
|
754
|
+
reminder_message = []
|
|
755
|
+
|
|
756
|
+
chunks.cur = cur_messages_post
|
|
757
|
+
chunks.reminder = []
|
|
674
758
|
|
|
675
759
|
# Make sure token counts are updated - using centralized method
|
|
676
760
|
# This also populates the context block cache
|
|
@@ -689,53 +773,46 @@ class AgentCoder(Coder):
|
|
|
689
773
|
# 1. Add relatively static blocks BEFORE done_messages
|
|
690
774
|
# These blocks change less frequently and can be part of the cacheable prefix
|
|
691
775
|
static_blocks = []
|
|
692
|
-
if
|
|
693
|
-
static_blocks.append(dir_structure)
|
|
694
|
-
if env_context:
|
|
776
|
+
if env_context and "environment_info" in self.allowed_context_blocks:
|
|
695
777
|
static_blocks.append(env_context)
|
|
778
|
+
if dir_structure and "directory_structure" in self.allowed_context_blocks:
|
|
779
|
+
static_blocks.append(dir_structure)
|
|
696
780
|
|
|
697
781
|
if static_blocks:
|
|
698
782
|
static_message = "\n\n".join(static_blocks)
|
|
699
783
|
# Insert as a system message right before done_messages
|
|
700
|
-
chunks.
|
|
784
|
+
chunks.system.append(dict(role="system", content=static_message))
|
|
701
785
|
|
|
702
786
|
# 2. Add dynamic blocks AFTER chat_files
|
|
703
787
|
# These blocks change with the current files in context
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
788
|
+
pre_dynamic_blocks = []
|
|
789
|
+
post_dynamic_blocks = []
|
|
790
|
+
if context_summary and "context_summary" in self.allowed_context_blocks:
|
|
791
|
+
pre_dynamic_blocks.append(context_summary)
|
|
792
|
+
if symbol_outline and "symbol_outline" in self.allowed_context_blocks:
|
|
793
|
+
pre_dynamic_blocks.append(symbol_outline)
|
|
794
|
+
if git_status and "git_status" in self.allowed_context_blocks:
|
|
795
|
+
pre_dynamic_blocks.append(git_status)
|
|
796
|
+
|
|
797
|
+
if todo_list and "todo_list" in self.allowed_context_blocks:
|
|
798
|
+
pre_dynamic_blocks.append(todo_list)
|
|
714
799
|
# Add tool usage context if there are repetitive tools
|
|
715
800
|
if hasattr(self, "tool_usage_history") and self.tool_usage_history:
|
|
716
801
|
repetitive_tools = self._get_repetitive_tools()
|
|
717
802
|
if repetitive_tools:
|
|
718
803
|
tool_context = self._generate_tool_context(repetitive_tools)
|
|
719
804
|
if tool_context:
|
|
720
|
-
|
|
805
|
+
pre_dynamic_blocks.append(tool_context)
|
|
721
806
|
|
|
722
|
-
if
|
|
723
|
-
dynamic_message = "\n\n".join(
|
|
724
|
-
# Append as a system message
|
|
725
|
-
chunks.
|
|
807
|
+
if pre_dynamic_blocks:
|
|
808
|
+
dynamic_message = "\n\n".join(pre_dynamic_blocks)
|
|
809
|
+
# Append as a system message on reminders
|
|
810
|
+
chunks.done.insert(0, dict(role="system", content=dynamic_message))
|
|
726
811
|
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
role="system", content=self.fmt_system_prompt(self.gpt_prompts.system_reminder)
|
|
732
|
-
),
|
|
733
|
-
]
|
|
734
|
-
else:
|
|
735
|
-
reminder_message = []
|
|
736
|
-
|
|
737
|
-
chunks.cur = list(self.cur_messages)
|
|
738
|
-
chunks.reminder = []
|
|
812
|
+
if post_dynamic_blocks:
|
|
813
|
+
dynamic_message = "\n\n".join(post_dynamic_blocks)
|
|
814
|
+
# Append as a system message on reminders
|
|
815
|
+
reminder_message.insert(0, dict(role="system", content=dynamic_message))
|
|
739
816
|
|
|
740
817
|
# Use accurate token counting method that considers enhanced context blocks
|
|
741
818
|
base_messages = chunks.all_messages()
|
|
@@ -780,6 +857,37 @@ class AgentCoder(Coder):
|
|
|
780
857
|
|
|
781
858
|
return chunks
|
|
782
859
|
|
|
860
|
+
def _update_edit_file_tracking(self, edit_file_names):
|
|
861
|
+
"""
|
|
862
|
+
Update tracking for last edited file and message divider for caching efficiency.
|
|
863
|
+
|
|
864
|
+
When the last edited file changes, we store the current message index minus 4
|
|
865
|
+
as a divider to split cur_messages, moving older messages to done_messages
|
|
866
|
+
for better caching.
|
|
867
|
+
"""
|
|
868
|
+
kept_messages = 8
|
|
869
|
+
if not edit_file_names:
|
|
870
|
+
self._cur_message_divider = 0
|
|
871
|
+
|
|
872
|
+
# Get the most recently edited file from the edit_file_names set
|
|
873
|
+
# We assume the first file in the sorted set is the most recent
|
|
874
|
+
sorted_edit_files = sorted(edit_file_names)
|
|
875
|
+
current_edited_file = sorted_edit_files[0] if sorted_edit_files else None
|
|
876
|
+
|
|
877
|
+
# Check if the last edited file has changed
|
|
878
|
+
if current_edited_file != self._last_edited_file:
|
|
879
|
+
# Store the new last edited file
|
|
880
|
+
self._last_edited_file = current_edited_file
|
|
881
|
+
|
|
882
|
+
# Calculate divider: current index minus last n messages
|
|
883
|
+
cur_messages_list = list(self.cur_messages)
|
|
884
|
+
if len(cur_messages_list) > kept_messages:
|
|
885
|
+
self._cur_message_divider = len(cur_messages_list) - kept_messages
|
|
886
|
+
else:
|
|
887
|
+
self._cur_message_divider = 0
|
|
888
|
+
|
|
889
|
+
return self._cur_message_divider
|
|
890
|
+
|
|
783
891
|
def get_context_summary(self):
|
|
784
892
|
"""
|
|
785
893
|
Generate a summary of the current context, including file content tokens and additional context blocks,
|
|
@@ -944,15 +1052,40 @@ class AgentCoder(Coder):
|
|
|
944
1052
|
"""
|
|
945
1053
|
Track tool usage before calling the base implementation.
|
|
946
1054
|
"""
|
|
947
|
-
self.
|
|
1055
|
+
self.agent_finished = False
|
|
1056
|
+
await self.auto_save_session()
|
|
1057
|
+
|
|
1058
|
+
# Clear last round tools and start tracking new round
|
|
1059
|
+
self.last_round_tools = []
|
|
948
1060
|
|
|
949
1061
|
if self.partial_response_tool_calls:
|
|
950
1062
|
for tool_call in self.partial_response_tool_calls:
|
|
951
|
-
|
|
1063
|
+
tool_name = tool_call.get("function", {}).get("name")
|
|
1064
|
+
|
|
1065
|
+
if tool_name:
|
|
1066
|
+
self.last_round_tools.append(tool_name)
|
|
1067
|
+
|
|
1068
|
+
# Create and store vector for this tool call
|
|
1069
|
+
# Remove id property if present before stringifying
|
|
1070
|
+
tool_call_copy = tool_call.copy()
|
|
1071
|
+
if "id" in tool_call_copy:
|
|
1072
|
+
del tool_call_copy["id"]
|
|
1073
|
+
tool_call_str = str(tool_call_copy) # Convert entire tool call to string
|
|
1074
|
+
tool_vector = create_bigram_vector((tool_call_str,))
|
|
1075
|
+
tool_vector_norm = normalize_vector(tool_vector)
|
|
1076
|
+
self.tool_call_vectors.append(tool_vector_norm)
|
|
1077
|
+
|
|
1078
|
+
# Add the completed round to history
|
|
1079
|
+
if self.last_round_tools:
|
|
1080
|
+
self.tool_usage_history += self.last_round_tools
|
|
1081
|
+
self.tool_usage_history = list(filter(None, self.tool_usage_history))
|
|
952
1082
|
|
|
953
1083
|
if len(self.tool_usage_history) > self.tool_usage_retries:
|
|
954
1084
|
self.tool_usage_history.pop(0)
|
|
955
1085
|
|
|
1086
|
+
if len(self.tool_call_vectors) > self.max_tool_vector_history:
|
|
1087
|
+
self.tool_call_vectors.pop(0)
|
|
1088
|
+
|
|
956
1089
|
return await super().process_tool_calls(tool_call_response)
|
|
957
1090
|
|
|
958
1091
|
async def reply_completed(self):
|
|
@@ -968,7 +1101,6 @@ class AgentCoder(Coder):
|
|
|
968
1101
|
a final answer to the user's question.
|
|
969
1102
|
"""
|
|
970
1103
|
# Legacy tool call processing for use_granular_editing=False
|
|
971
|
-
self.agent_finished = False
|
|
972
1104
|
content = self.partial_response_content
|
|
973
1105
|
if not content or not content.strip():
|
|
974
1106
|
if len(self.tool_usage_history) > self.tool_usage_retries:
|
|
@@ -1136,8 +1268,8 @@ class AgentCoder(Coder):
|
|
|
1136
1268
|
str: Result message
|
|
1137
1269
|
"""
|
|
1138
1270
|
# Check if tool exists in registry
|
|
1139
|
-
if norm_tool_name in self.
|
|
1140
|
-
tool_module = self.
|
|
1271
|
+
if norm_tool_name in self.tool_registry:
|
|
1272
|
+
tool_module = self.tool_registry[norm_tool_name]
|
|
1141
1273
|
try:
|
|
1142
1274
|
# Use the process_response function from the tool module
|
|
1143
1275
|
result = tool_module.process_response(self, params)
|
|
@@ -1163,6 +1295,73 @@ class AgentCoder(Coder):
|
|
|
1163
1295
|
|
|
1164
1296
|
return f"Error: Unknown tool name '{norm_tool_name}'"
|
|
1165
1297
|
|
|
1298
|
+
def _convert_concatenated_json_to_tool_calls(self, content):
|
|
1299
|
+
"""
|
|
1300
|
+
Check if content contains concatenated JSON objects and convert them to tool call format.
|
|
1301
|
+
|
|
1302
|
+
Args:
|
|
1303
|
+
content (str): Content to check for concatenated JSON
|
|
1304
|
+
|
|
1305
|
+
Returns:
|
|
1306
|
+
str: Content with concatenated JSON converted to tool call format, or original content if no JSON found
|
|
1307
|
+
"""
|
|
1308
|
+
try:
|
|
1309
|
+
# Use split_concatenated_json to detect and split concatenated JSON objects
|
|
1310
|
+
json_chunks = utils.split_concatenated_json(content)
|
|
1311
|
+
|
|
1312
|
+
# If we found multiple JSON objects, convert them to tool call format
|
|
1313
|
+
if len(json_chunks) >= 1:
|
|
1314
|
+
tool_calls = []
|
|
1315
|
+
for chunk in json_chunks:
|
|
1316
|
+
try:
|
|
1317
|
+
json_obj = json.loads(chunk)
|
|
1318
|
+
# Check if this looks like a tool call JSON object
|
|
1319
|
+
if (
|
|
1320
|
+
isinstance(json_obj, dict)
|
|
1321
|
+
and "name" in json_obj
|
|
1322
|
+
and "arguments" in json_obj
|
|
1323
|
+
):
|
|
1324
|
+
tool_name = json_obj["name"]
|
|
1325
|
+
arguments = json_obj["arguments"]
|
|
1326
|
+
|
|
1327
|
+
# Convert arguments dictionary to keyword arguments string
|
|
1328
|
+
kw_args = []
|
|
1329
|
+
for key, value in arguments.items():
|
|
1330
|
+
if isinstance(value, str):
|
|
1331
|
+
# Escape quotes and wrap in quotes
|
|
1332
|
+
escaped_value = value.replace('"', '\\"')
|
|
1333
|
+
kw_args.append(f'{key}="{escaped_value}"')
|
|
1334
|
+
elif isinstance(value, bool):
|
|
1335
|
+
kw_args.append(f"{key}={str(value).lower()}")
|
|
1336
|
+
elif value is None:
|
|
1337
|
+
kw_args.append(f"{key}=None")
|
|
1338
|
+
else:
|
|
1339
|
+
# For numbers and other types, use repr for safe representation
|
|
1340
|
+
kw_args.append(f"{key}={repr(value)}")
|
|
1341
|
+
|
|
1342
|
+
# Join keyword arguments
|
|
1343
|
+
kw_args_str = ", ".join(kw_args)
|
|
1344
|
+
|
|
1345
|
+
# Convert to [tool_call(ToolName, key1="value1", key2="value2")] format
|
|
1346
|
+
tool_call = f"[tool_call({tool_name}, {kw_args_str})]"
|
|
1347
|
+
tool_calls.append(tool_call)
|
|
1348
|
+
else:
|
|
1349
|
+
# Not a tool call JSON, keep as is
|
|
1350
|
+
tool_calls.append(chunk)
|
|
1351
|
+
except json.JSONDecodeError:
|
|
1352
|
+
# Invalid JSON, keep as is
|
|
1353
|
+
tool_calls.append(chunk)
|
|
1354
|
+
|
|
1355
|
+
# If we found any tool calls, replace the content
|
|
1356
|
+
if any(call.startswith("[tool_") for call in tool_calls):
|
|
1357
|
+
return "".join(tool_calls)
|
|
1358
|
+
|
|
1359
|
+
except Exception as e:
|
|
1360
|
+
# If anything goes wrong, return original content
|
|
1361
|
+
self.io.tool_warning(f"Error converting concatenated JSON to tool calls: {str(e)}")
|
|
1362
|
+
|
|
1363
|
+
return content
|
|
1364
|
+
|
|
1166
1365
|
async def _process_tool_commands(self, content):
|
|
1167
1366
|
"""
|
|
1168
1367
|
Process tool commands in the `[tool_call(name, param=value)]` format within the content.
|
|
@@ -1182,20 +1381,31 @@ class AgentCoder(Coder):
|
|
|
1182
1381
|
max_calls = self.max_tool_calls
|
|
1183
1382
|
tool_names = []
|
|
1184
1383
|
|
|
1384
|
+
# Check if content contains concatenated JSON and convert to tool call format
|
|
1385
|
+
content = self._convert_concatenated_json_to_tool_calls(content)
|
|
1386
|
+
|
|
1185
1387
|
# Check if there's a '---' separator and only process tool calls after the LAST one
|
|
1186
1388
|
separator_marker = "---"
|
|
1187
1389
|
content_parts = content.split(separator_marker)
|
|
1188
1390
|
|
|
1189
1391
|
# If there's no separator, treat the entire content as before the separator
|
|
1392
|
+
# But only return immediately if no tool calls were found in the JSON conversion
|
|
1190
1393
|
if len(content_parts) == 1:
|
|
1191
|
-
#
|
|
1192
|
-
|
|
1394
|
+
# Check if we have any tool calls in the content after JSON conversion
|
|
1395
|
+
# If we have tool calls, we should process them even without a separator
|
|
1396
|
+
tool_call_pattern = r"\[tool_call\([^\]]+\)\]"
|
|
1397
|
+
if re.search(tool_call_pattern, content):
|
|
1398
|
+
# We have tool calls, so continue processing
|
|
1399
|
+
content_before_separator = ""
|
|
1400
|
+
content_after_separator = content
|
|
1401
|
+
else:
|
|
1402
|
+
# No tool calls found, return the original content
|
|
1403
|
+
return content, result_messages, False, content, tool_names
|
|
1193
1404
|
|
|
1194
1405
|
# Take everything before the last separator (including intermediate separators)
|
|
1195
1406
|
content_before_separator = separator_marker.join(content_parts[:-1])
|
|
1196
1407
|
# Take only what comes after the last separator
|
|
1197
1408
|
content_after_separator = content_parts[-1]
|
|
1198
|
-
|
|
1199
1409
|
# Find tool calls using a more robust method, but only in the content after separator
|
|
1200
1410
|
processed_content = content_before_separator + separator_marker
|
|
1201
1411
|
last_index = 0
|
|
@@ -1492,13 +1702,14 @@ class AgentCoder(Coder):
|
|
|
1492
1702
|
|
|
1493
1703
|
def _get_repetitive_tools(self):
|
|
1494
1704
|
"""
|
|
1495
|
-
Identifies repetitive tool usage patterns from
|
|
1705
|
+
Identifies repetitive tool usage patterns from rounds of tool calls.
|
|
1496
1706
|
|
|
1497
|
-
This method
|
|
1498
|
-
1. If the last
|
|
1499
|
-
2. It checks for any read tool that has been used 2 or more times
|
|
1707
|
+
This method combines count-based and similarity-based detection:
|
|
1708
|
+
1. If the last round contained a write tool, it assumes progress and returns no repetitive tools.
|
|
1709
|
+
2. It checks for any read tool that has been used 2 or more times across rounds.
|
|
1500
1710
|
3. If no tools are repeated, but all tools in the history are read tools,
|
|
1501
1711
|
it flags all of them as potentially repetitive.
|
|
1712
|
+
4. It checks for similarity-based repetition using cosine similarity on tool call strings.
|
|
1502
1713
|
|
|
1503
1714
|
It avoids flagging repetition if a "write" tool was used recently,
|
|
1504
1715
|
as that suggests progress is being made.
|
|
@@ -1509,31 +1720,71 @@ class AgentCoder(Coder):
|
|
|
1509
1720
|
if history_len < 2:
|
|
1510
1721
|
return set()
|
|
1511
1722
|
|
|
1512
|
-
#
|
|
1513
|
-
|
|
1514
|
-
|
|
1723
|
+
# Check for similarity-based repetition
|
|
1724
|
+
similarity_repetitive_tools = self._get_repetitive_tools_by_similarity()
|
|
1725
|
+
|
|
1726
|
+
# Flatten the tool usage history for count-based analysis
|
|
1727
|
+
all_tools = []
|
|
1728
|
+
for round_tools in self.tool_usage_history:
|
|
1729
|
+
all_tools.extend(round_tools)
|
|
1515
1730
|
|
|
1516
|
-
|
|
1731
|
+
# If the last round contained a write tool, we're likely making progress.
|
|
1732
|
+
if self.last_round_tools:
|
|
1733
|
+
last_round_has_write = any(
|
|
1734
|
+
tool.lower() in self.write_tools for tool in self.last_round_tools
|
|
1735
|
+
)
|
|
1736
|
+
if last_round_has_write:
|
|
1517
1737
|
self.tool_usage_history = []
|
|
1518
|
-
return set()
|
|
1738
|
+
return similarity_repetitive_tools if len(similarity_repetitive_tools) else set()
|
|
1519
1739
|
|
|
1520
1740
|
# If all tools in history are read tools, return all of them
|
|
1521
|
-
if all(tool.lower() in self.read_tools for tool in
|
|
1522
|
-
return set(
|
|
1741
|
+
if all(tool.lower() in self.read_tools for tool in all_tools):
|
|
1742
|
+
return set(all_tools)
|
|
1523
1743
|
|
|
1524
|
-
# Check for any read tool used more than once
|
|
1525
|
-
tool_counts = Counter(
|
|
1526
|
-
|
|
1744
|
+
# Check for any read tool used more than once across rounds
|
|
1745
|
+
tool_counts = Counter(all_tools)
|
|
1746
|
+
count_repetitive_tools = {
|
|
1527
1747
|
tool
|
|
1528
1748
|
for tool, count in tool_counts.items()
|
|
1529
1749
|
if count >= 2 and tool.lower() in self.read_tools
|
|
1530
1750
|
}
|
|
1531
1751
|
|
|
1752
|
+
# Combine both detection methods
|
|
1753
|
+
repetitive_tools = count_repetitive_tools.union(similarity_repetitive_tools)
|
|
1754
|
+
|
|
1532
1755
|
if repetitive_tools:
|
|
1533
1756
|
return repetitive_tools
|
|
1534
1757
|
|
|
1535
1758
|
return set()
|
|
1536
1759
|
|
|
1760
|
+
def _get_repetitive_tools_by_similarity(self):
|
|
1761
|
+
"""
|
|
1762
|
+
Identifies repetitive tool usage patterns using cosine similarity on tool call strings.
|
|
1763
|
+
|
|
1764
|
+
This method checks if the latest tool calls are highly similar (>0.99 threshold)
|
|
1765
|
+
to historical tool calls using bigram vector similarity.
|
|
1766
|
+
|
|
1767
|
+
Returns:
|
|
1768
|
+
set: Set of tool names that are repetitive based on similarity
|
|
1769
|
+
"""
|
|
1770
|
+
if not self.tool_usage_history or len(self.tool_call_vectors) < 2:
|
|
1771
|
+
return set()
|
|
1772
|
+
|
|
1773
|
+
# Get the latest tool call vector
|
|
1774
|
+
latest_vector = self.tool_call_vectors[-1]
|
|
1775
|
+
|
|
1776
|
+
# Check similarity against historical vectors (excluding the latest)
|
|
1777
|
+
for i, historical_vector in enumerate(self.tool_call_vectors[:-1]):
|
|
1778
|
+
similarity = cosine_similarity(latest_vector, historical_vector)
|
|
1779
|
+
|
|
1780
|
+
# If similarity is high enough, flag as repetitive
|
|
1781
|
+
if similarity >= self.tool_similarity_threshold:
|
|
1782
|
+
# Return the tool name from the corresponding position in history
|
|
1783
|
+
if i < len(self.tool_usage_history):
|
|
1784
|
+
return {self.tool_usage_history[i]}
|
|
1785
|
+
|
|
1786
|
+
return set()
|
|
1787
|
+
|
|
1537
1788
|
def _generate_tool_context(self, repetitive_tools):
|
|
1538
1789
|
"""
|
|
1539
1790
|
Generate a context message for the LLM about recent tool usage.
|
|
@@ -1546,8 +1797,7 @@ class AgentCoder(Coder):
|
|
|
1546
1797
|
# Add turn and tool call statistics
|
|
1547
1798
|
context_parts.append("## Turn and Tool Call Statistics")
|
|
1548
1799
|
context_parts.append(f"- Current turn: {self.num_reflections + 1}")
|
|
1549
|
-
context_parts.append(f"-
|
|
1550
|
-
context_parts.append(f"- Total tool calls in session: {self.num_tool_calls}")
|
|
1800
|
+
context_parts.append(f"- Total tool calls this turn: {self.num_tool_calls}")
|
|
1551
1801
|
context_parts.append("\n\n")
|
|
1552
1802
|
|
|
1553
1803
|
# Add recent tool usage history
|
|
@@ -1572,7 +1822,9 @@ class AgentCoder(Coder):
|
|
|
1572
1822
|
for tool in repetitive_tools:
|
|
1573
1823
|
context_parts.append(f"- `{tool}`")
|
|
1574
1824
|
context_parts.append(
|
|
1575
|
-
"Your exploration appears to be stuck in a loop. Please try a different approach
|
|
1825
|
+
"Your exploration appears to be stuck in a loop. Please try a different approach."
|
|
1826
|
+
" Use the `Thinking` tool to clarify your intentions and new approach to"
|
|
1827
|
+
" what you are currently attempting to accomplish."
|
|
1576
1828
|
)
|
|
1577
1829
|
context_parts.append("\n")
|
|
1578
1830
|
context_parts.append("**Suggestions for alternative approaches:**")
|
|
@@ -1914,7 +2166,7 @@ Just reply with fixed versions of the {blocks} above that failed to match.
|
|
|
1914
2166
|
if line.startswith("??"):
|
|
1915
2167
|
# Extract the filename (remove the '?? ' prefix)
|
|
1916
2168
|
untracked_file = line[3:]
|
|
1917
|
-
if not self.repo.
|
|
2169
|
+
if not self.repo.ignored_file(untracked_file):
|
|
1918
2170
|
untracked_files.append(untracked_file)
|
|
1919
2171
|
except Exception as e:
|
|
1920
2172
|
self.io.tool_warning(f"Error getting untracked files: {str(e)}")
|
|
@@ -2000,7 +2252,7 @@ Just reply with fixed versions of the {blocks} above that failed to match.
|
|
|
2000
2252
|
if not os.path.isfile(abs_path):
|
|
2001
2253
|
return (
|
|
2002
2254
|
'<context name="todo_list">\n'
|
|
2003
|
-
"Todo list does not exist. Please update it."
|
|
2255
|
+
"Todo list does not exist. Please update it with the `UpdataTodoList` tool."
|
|
2004
2256
|
"</context>"
|
|
2005
2257
|
)
|
|
2006
2258
|
|
|
@@ -2012,7 +2264,7 @@ Just reply with fixed versions of the {blocks} above that failed to match.
|
|
|
2012
2264
|
# Format the todo list context block
|
|
2013
2265
|
result = '<context name="todo_list">\n'
|
|
2014
2266
|
result += "## Current Todo List\n\n"
|
|
2015
|
-
result += "Below is the current todo list managed via `UpdateTodoList` tool:\n\n"
|
|
2267
|
+
result += "Below is the current todo list managed via the `UpdateTodoList` tool:\n\n"
|
|
2016
2268
|
result += f"```\n{content}\n```\n"
|
|
2017
2269
|
result += "</context>"
|
|
2018
2270
|
|