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/tools/replace_lines.py
CHANGED
|
@@ -1,217 +1,180 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
from .tool_utils import (
|
|
1
|
+
from aider.tools.utils.base_tool import BaseTool
|
|
2
|
+
from aider.tools.utils.helpers import (
|
|
4
3
|
ToolError,
|
|
5
4
|
apply_change,
|
|
6
5
|
format_tool_result,
|
|
7
6
|
generate_unified_diff_snippet,
|
|
8
7
|
handle_tool_error,
|
|
8
|
+
validate_file_for_edit,
|
|
9
9
|
)
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
"
|
|
17
|
-
|
|
18
|
-
"
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
|
|
24
|
-
|
|
10
|
+
from aider.tools.utils.output import tool_body_unwrapped, tool_footer, tool_header
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Tool(BaseTool):
|
|
14
|
+
NORM_NAME = "replacelines"
|
|
15
|
+
SCHEMA = {
|
|
16
|
+
"type": "function",
|
|
17
|
+
"function": {
|
|
18
|
+
"name": "ReplaceLines",
|
|
19
|
+
"description": "Replace a range of lines in a file.",
|
|
20
|
+
"parameters": {
|
|
21
|
+
"type": "object",
|
|
22
|
+
"properties": {
|
|
23
|
+
"file_path": {"type": "string"},
|
|
24
|
+
"start_line": {"type": "integer"},
|
|
25
|
+
"end_line": {"type": "integer"},
|
|
26
|
+
"new_content": {"type": "string"},
|
|
27
|
+
"change_id": {"type": "string"},
|
|
28
|
+
"dry_run": {"type": "boolean", "default": False},
|
|
29
|
+
},
|
|
30
|
+
"required": ["file_path", "start_line", "end_line", "new_content"],
|
|
25
31
|
},
|
|
26
|
-
"required": ["file_path", "start_line", "end_line", "new_content"],
|
|
27
32
|
},
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
# Normalized tool name for lookup
|
|
32
|
-
NORM_NAME = "replacelines"
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
def _execute_replace_lines(
|
|
36
|
-
coder, file_path, start_line, end_line, new_content, change_id=None, dry_run=False
|
|
37
|
-
):
|
|
38
|
-
"""
|
|
39
|
-
Replace a range of lines identified by line numbers.
|
|
40
|
-
Useful for fixing errors identified by error messages or linters.
|
|
41
|
-
|
|
42
|
-
Parameters:
|
|
43
|
-
- file_path: Path to the file to modify
|
|
44
|
-
- start_line: The first line number to replace (1-based)
|
|
45
|
-
- end_line: The last line number to replace (1-based)
|
|
46
|
-
- new_content: New content for the lines (can be multi-line)
|
|
47
|
-
- change_id: Optional ID for tracking the change
|
|
48
|
-
- dry_run: If True, simulate the change without modifying the file
|
|
49
|
-
|
|
50
|
-
Returns a result message.
|
|
51
|
-
"""
|
|
52
|
-
tool_name = "ReplaceLines"
|
|
53
|
-
try:
|
|
54
|
-
# Get absolute file path
|
|
55
|
-
abs_path = coder.abs_root_path(file_path)
|
|
56
|
-
rel_path = coder.get_rel_fname(abs_path)
|
|
57
|
-
|
|
58
|
-
# Check if file exists
|
|
59
|
-
if not os.path.isfile(abs_path):
|
|
60
|
-
raise ToolError(f"File '{file_path}' not found")
|
|
61
|
-
|
|
62
|
-
# Check if file is in editable context
|
|
63
|
-
if abs_path not in coder.abs_fnames:
|
|
64
|
-
if abs_path in coder.abs_read_only_fnames:
|
|
65
|
-
raise ToolError(f"File '{file_path}' is read-only. Use MakeEditable first.")
|
|
66
|
-
else:
|
|
67
|
-
raise ToolError(f"File '{file_path}' not in context")
|
|
68
|
-
|
|
69
|
-
# Reread file content immediately before modification
|
|
70
|
-
file_content = coder.io.read_text(abs_path)
|
|
71
|
-
if file_content is None:
|
|
72
|
-
raise ToolError(f"Could not read file '{file_path}'")
|
|
73
|
-
|
|
74
|
-
# Convert line numbers to integers if needed
|
|
75
|
-
try:
|
|
76
|
-
start_line = int(start_line)
|
|
77
|
-
except ValueError:
|
|
78
|
-
raise ToolError(f"Invalid start_line value: '{start_line}'. Must be an integer.")
|
|
33
|
+
}
|
|
79
34
|
|
|
35
|
+
@classmethod
|
|
36
|
+
def execute(
|
|
37
|
+
cls, coder, file_path, start_line, end_line, new_content, change_id=None, dry_run=False
|
|
38
|
+
):
|
|
39
|
+
"""
|
|
40
|
+
Replace a range of lines identified by line numbers.
|
|
41
|
+
Useful for fixing errors identified by error messages or linters.
|
|
42
|
+
|
|
43
|
+
Parameters:
|
|
44
|
+
- file_path: Path to the file to modify
|
|
45
|
+
- start_line: The first line number to replace (1-based)
|
|
46
|
+
- end_line: The last line number to replace (1-based)
|
|
47
|
+
- new_content: New content for the lines (can be multi-line)
|
|
48
|
+
- change_id: Optional ID for tracking the change
|
|
49
|
+
- dry_run: If True, simulate the change without modifying the file
|
|
50
|
+
|
|
51
|
+
Returns a result message.
|
|
52
|
+
"""
|
|
53
|
+
tool_name = "ReplaceLines"
|
|
80
54
|
try:
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
55
|
+
# 1. Validate file and get content
|
|
56
|
+
abs_path, rel_path, original_content = validate_file_for_edit(coder, file_path)
|
|
57
|
+
|
|
58
|
+
# Convert line numbers to integers if needed
|
|
59
|
+
try:
|
|
60
|
+
start_line = int(start_line)
|
|
61
|
+
except ValueError:
|
|
62
|
+
raise ToolError(f"Invalid start_line value: '{start_line}'. Must be an integer.")
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
end_line = int(end_line)
|
|
66
|
+
except ValueError:
|
|
67
|
+
raise ToolError(f"Invalid end_line value: '{end_line}'. Must be an integer.")
|
|
68
|
+
|
|
69
|
+
# Split into lines
|
|
70
|
+
lines = original_content.splitlines()
|
|
71
|
+
|
|
72
|
+
# Convert 1-based line numbers to 0-based indices
|
|
73
|
+
start_idx = start_line - 1
|
|
74
|
+
end_idx = end_line - 1
|
|
75
|
+
|
|
76
|
+
# Validate line numbers
|
|
77
|
+
if start_idx < 0 or start_idx >= len(lines):
|
|
78
|
+
raise ToolError(
|
|
79
|
+
f"Start line {start_line} is out of range for file '{file_path}' (has"
|
|
80
|
+
f" {len(lines)} lines)."
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
if end_idx < start_idx or end_idx >= len(lines):
|
|
84
|
+
raise ToolError(
|
|
85
|
+
f"End line {end_line} is out of range for file '{file_path}' (must be >= start"
|
|
86
|
+
f" line {start_line} and <= {len(lines)})."
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
# Store original content for change tracking
|
|
90
|
+
replaced_lines = lines[start_idx : end_idx + 1]
|
|
91
|
+
|
|
92
|
+
# Split the new content into lines
|
|
93
|
+
new_lines = new_content.splitlines()
|
|
94
|
+
|
|
95
|
+
# Perform the replacement
|
|
96
|
+
new_full_lines = lines[:start_idx] + new_lines + lines[end_idx + 1 :]
|
|
97
|
+
new_content_full = "\n".join(new_full_lines)
|
|
98
|
+
|
|
99
|
+
if original_content == new_content_full:
|
|
100
|
+
coder.io.tool_warning("No changes made: new content is identical to original")
|
|
101
|
+
return "Warning: No changes made (new content identical to original)"
|
|
102
|
+
|
|
103
|
+
# Generate diff snippet
|
|
104
|
+
diff_snippet = generate_unified_diff_snippet(
|
|
105
|
+
original_content, new_content_full, rel_path
|
|
97
106
|
)
|
|
98
107
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
108
|
+
# Create a readable diff for the lines replacement
|
|
109
|
+
diff = f"Lines {start_line}-{end_line}:\n"
|
|
110
|
+
# Add removed lines with - prefix
|
|
111
|
+
for line in replaced_lines:
|
|
112
|
+
diff += f"- {line}\n"
|
|
113
|
+
# Add separator
|
|
114
|
+
diff += "---\n"
|
|
115
|
+
# Add new lines with + prefix
|
|
116
|
+
for line in new_lines:
|
|
117
|
+
diff += f"+ {line}\n"
|
|
118
|
+
|
|
119
|
+
# Handle dry run
|
|
120
|
+
if dry_run:
|
|
121
|
+
dry_run_message = (
|
|
122
|
+
f"Dry run: Would replace lines {start_line}-{end_line} in {file_path}"
|
|
123
|
+
)
|
|
124
|
+
return format_tool_result(
|
|
125
|
+
coder,
|
|
126
|
+
tool_name,
|
|
127
|
+
"",
|
|
128
|
+
dry_run=True,
|
|
129
|
+
dry_run_message=dry_run_message,
|
|
130
|
+
diff_snippet=diff_snippet,
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
# --- Apply Change (Not dry run) ---
|
|
134
|
+
metadata = {
|
|
135
|
+
"start_line": start_line,
|
|
136
|
+
"end_line": end_line,
|
|
137
|
+
"replaced_lines": replaced_lines,
|
|
138
|
+
"new_lines": new_lines,
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
final_change_id = apply_change(
|
|
142
|
+
coder,
|
|
143
|
+
abs_path,
|
|
144
|
+
rel_path,
|
|
145
|
+
original_content,
|
|
146
|
+
new_content_full,
|
|
147
|
+
"replacelines",
|
|
148
|
+
metadata,
|
|
149
|
+
change_id,
|
|
103
150
|
)
|
|
104
151
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
new_content_full = "\n".join(new_full_lines)
|
|
115
|
-
|
|
116
|
-
if original_content == new_content_full:
|
|
117
|
-
coder.io.tool_warning("No changes made: new content is identical to original")
|
|
118
|
-
return "Warning: No changes made (new content identical to original)"
|
|
119
|
-
|
|
120
|
-
# Generate diff snippet
|
|
121
|
-
diff_snippet = generate_unified_diff_snippet(original_content, new_content_full, rel_path)
|
|
122
|
-
|
|
123
|
-
# Create a readable diff for the lines replacement
|
|
124
|
-
diff = f"Lines {start_line}-{end_line}:\n"
|
|
125
|
-
# Add removed lines with - prefix
|
|
126
|
-
for line in replaced_lines:
|
|
127
|
-
diff += f"- {line}\n"
|
|
128
|
-
# Add separator
|
|
129
|
-
diff += "---\n"
|
|
130
|
-
# Add new lines with + prefix
|
|
131
|
-
for line in new_lines:
|
|
132
|
-
diff += f"+ {line}\n"
|
|
133
|
-
|
|
134
|
-
# Handle dry run
|
|
135
|
-
if dry_run:
|
|
136
|
-
dry_run_message = f"Dry run: Would replace lines {start_line}-{end_line} in {file_path}"
|
|
152
|
+
coder.files_edited_by_tools.add(rel_path)
|
|
153
|
+
replaced_count = end_line - start_line + 1
|
|
154
|
+
new_count = len(new_lines)
|
|
155
|
+
|
|
156
|
+
# Format and return result
|
|
157
|
+
success_message = (
|
|
158
|
+
f"Replaced lines {start_line}-{end_line} ({replaced_count} lines) with {new_count}"
|
|
159
|
+
f" new lines in {file_path}"
|
|
160
|
+
)
|
|
137
161
|
return format_tool_result(
|
|
138
162
|
coder,
|
|
139
163
|
tool_name,
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
dry_run_message=dry_run_message,
|
|
164
|
+
success_message,
|
|
165
|
+
change_id=final_change_id,
|
|
143
166
|
diff_snippet=diff_snippet,
|
|
144
167
|
)
|
|
145
168
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
original_content,
|
|
159
|
-
new_content_full,
|
|
160
|
-
"replacelines",
|
|
161
|
-
metadata,
|
|
162
|
-
change_id,
|
|
163
|
-
)
|
|
164
|
-
|
|
165
|
-
coder.files_edited_by_tools.add(rel_path)
|
|
166
|
-
replaced_count = end_line - start_line + 1
|
|
167
|
-
new_count = len(new_lines)
|
|
168
|
-
|
|
169
|
-
# Format and return result
|
|
170
|
-
success_message = (
|
|
171
|
-
f"Replaced lines {start_line}-{end_line} ({replaced_count} lines) with {new_count} new"
|
|
172
|
-
f" lines in {file_path}"
|
|
173
|
-
)
|
|
174
|
-
return format_tool_result(
|
|
175
|
-
coder, tool_name, success_message, change_id=final_change_id, diff_snippet=diff_snippet
|
|
176
|
-
)
|
|
177
|
-
|
|
178
|
-
except ToolError as e:
|
|
179
|
-
# Handle errors raised by utility functions (expected errors)
|
|
180
|
-
return handle_tool_error(coder, tool_name, e, add_traceback=False)
|
|
181
|
-
except Exception as e:
|
|
182
|
-
# Handle unexpected errors
|
|
183
|
-
return handle_tool_error(coder, tool_name, e)
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
def process_response(coder, params):
|
|
187
|
-
"""
|
|
188
|
-
Process the ReplaceLines tool response.
|
|
189
|
-
|
|
190
|
-
Args:
|
|
191
|
-
coder: The Coder instance
|
|
192
|
-
params: Dictionary of parameters
|
|
193
|
-
|
|
194
|
-
Returns:
|
|
195
|
-
str: Result message
|
|
196
|
-
"""
|
|
197
|
-
file_path = params.get("file_path")
|
|
198
|
-
start_line = params.get("start_line")
|
|
199
|
-
end_line = params.get("end_line")
|
|
200
|
-
new_content = params.get("new_content")
|
|
201
|
-
change_id = params.get("change_id")
|
|
202
|
-
dry_run = params.get("dry_run", False)
|
|
203
|
-
|
|
204
|
-
if (
|
|
205
|
-
file_path is not None
|
|
206
|
-
and start_line is not None
|
|
207
|
-
and end_line is not None
|
|
208
|
-
and new_content is not None
|
|
209
|
-
):
|
|
210
|
-
return _execute_replace_lines(
|
|
211
|
-
coder, file_path, start_line, end_line, new_content, change_id, dry_run
|
|
212
|
-
)
|
|
213
|
-
else:
|
|
214
|
-
return (
|
|
215
|
-
"Error: Missing required parameters for ReplaceLines (file_path,"
|
|
216
|
-
" start_line, end_line, new_content)"
|
|
217
|
-
)
|
|
169
|
+
except ToolError as e:
|
|
170
|
+
# Handle errors raised by utility functions (expected errors)
|
|
171
|
+
return handle_tool_error(coder, tool_name, e, add_traceback=False)
|
|
172
|
+
except Exception as e:
|
|
173
|
+
# Handle unexpected errors
|
|
174
|
+
return handle_tool_error(coder, tool_name, e)
|
|
175
|
+
|
|
176
|
+
@classmethod
|
|
177
|
+
def format_output(cls, coder, mcp_server, tool_response):
|
|
178
|
+
tool_header(coder=coder, mcp_server=mcp_server, tool_response=tool_response)
|
|
179
|
+
tool_body_unwrapped(coder=coder, tool_response=tool_response)
|
|
180
|
+
tool_footer(coder=coder, tool_response=tool_response)
|