aider-ce 0.88.20__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 +20 -0
- aider/__main__.py +4 -0
- aider/_version.py +34 -0
- aider/analytics.py +258 -0
- aider/args.py +1056 -0
- aider/args_formatter.py +228 -0
- aider/change_tracker.py +133 -0
- aider/coders/__init__.py +36 -0
- aider/coders/agent_coder.py +2166 -0
- aider/coders/agent_prompts.py +104 -0
- aider/coders/architect_coder.py +48 -0
- aider/coders/architect_prompts.py +40 -0
- aider/coders/ask_coder.py +9 -0
- aider/coders/ask_prompts.py +35 -0
- aider/coders/base_coder.py +3613 -0
- aider/coders/base_prompts.py +87 -0
- aider/coders/chat_chunks.py +64 -0
- aider/coders/context_coder.py +53 -0
- aider/coders/context_prompts.py +75 -0
- aider/coders/editblock_coder.py +657 -0
- aider/coders/editblock_fenced_coder.py +10 -0
- aider/coders/editblock_fenced_prompts.py +143 -0
- aider/coders/editblock_func_coder.py +141 -0
- aider/coders/editblock_func_prompts.py +27 -0
- aider/coders/editblock_prompts.py +175 -0
- aider/coders/editor_diff_fenced_coder.py +9 -0
- aider/coders/editor_diff_fenced_prompts.py +11 -0
- aider/coders/editor_editblock_coder.py +9 -0
- aider/coders/editor_editblock_prompts.py +21 -0
- aider/coders/editor_whole_coder.py +9 -0
- aider/coders/editor_whole_prompts.py +12 -0
- aider/coders/help_coder.py +16 -0
- aider/coders/help_prompts.py +46 -0
- aider/coders/patch_coder.py +706 -0
- aider/coders/patch_prompts.py +159 -0
- aider/coders/search_replace.py +757 -0
- aider/coders/shell.py +37 -0
- aider/coders/single_wholefile_func_coder.py +102 -0
- aider/coders/single_wholefile_func_prompts.py +27 -0
- aider/coders/udiff_coder.py +429 -0
- aider/coders/udiff_prompts.py +115 -0
- aider/coders/udiff_simple.py +14 -0
- aider/coders/udiff_simple_prompts.py +25 -0
- aider/coders/wholefile_coder.py +144 -0
- aider/coders/wholefile_func_coder.py +134 -0
- aider/coders/wholefile_func_prompts.py +27 -0
- aider/coders/wholefile_prompts.py +65 -0
- aider/commands.py +2173 -0
- aider/copypaste.py +72 -0
- aider/deprecated.py +126 -0
- aider/diffs.py +128 -0
- aider/dump.py +29 -0
- aider/editor.py +147 -0
- aider/exceptions.py +115 -0
- aider/format_settings.py +26 -0
- aider/gui.py +545 -0
- aider/help.py +163 -0
- aider/help_pats.py +19 -0
- aider/helpers/__init__.py +9 -0
- aider/helpers/similarity.py +98 -0
- aider/history.py +180 -0
- aider/io.py +1608 -0
- aider/linter.py +304 -0
- aider/llm.py +55 -0
- aider/main.py +1415 -0
- aider/mcp/__init__.py +174 -0
- aider/mcp/server.py +149 -0
- aider/mdstream.py +243 -0
- aider/models.py +1313 -0
- aider/onboarding.py +429 -0
- aider/openrouter.py +129 -0
- aider/prompts.py +56 -0
- aider/queries/tree-sitter-language-pack/README.md +7 -0
- aider/queries/tree-sitter-language-pack/arduino-tags.scm +5 -0
- aider/queries/tree-sitter-language-pack/c-tags.scm +9 -0
- aider/queries/tree-sitter-language-pack/chatito-tags.scm +16 -0
- aider/queries/tree-sitter-language-pack/clojure-tags.scm +7 -0
- aider/queries/tree-sitter-language-pack/commonlisp-tags.scm +122 -0
- aider/queries/tree-sitter-language-pack/cpp-tags.scm +15 -0
- aider/queries/tree-sitter-language-pack/csharp-tags.scm +26 -0
- aider/queries/tree-sitter-language-pack/d-tags.scm +26 -0
- aider/queries/tree-sitter-language-pack/dart-tags.scm +92 -0
- aider/queries/tree-sitter-language-pack/elisp-tags.scm +5 -0
- aider/queries/tree-sitter-language-pack/elixir-tags.scm +54 -0
- aider/queries/tree-sitter-language-pack/elm-tags.scm +19 -0
- aider/queries/tree-sitter-language-pack/gleam-tags.scm +41 -0
- aider/queries/tree-sitter-language-pack/go-tags.scm +42 -0
- aider/queries/tree-sitter-language-pack/java-tags.scm +20 -0
- aider/queries/tree-sitter-language-pack/javascript-tags.scm +88 -0
- aider/queries/tree-sitter-language-pack/lua-tags.scm +34 -0
- aider/queries/tree-sitter-language-pack/matlab-tags.scm +10 -0
- aider/queries/tree-sitter-language-pack/ocaml-tags.scm +115 -0
- aider/queries/tree-sitter-language-pack/ocaml_interface-tags.scm +98 -0
- aider/queries/tree-sitter-language-pack/pony-tags.scm +39 -0
- aider/queries/tree-sitter-language-pack/properties-tags.scm +5 -0
- aider/queries/tree-sitter-language-pack/python-tags.scm +14 -0
- aider/queries/tree-sitter-language-pack/r-tags.scm +21 -0
- aider/queries/tree-sitter-language-pack/racket-tags.scm +12 -0
- aider/queries/tree-sitter-language-pack/ruby-tags.scm +64 -0
- aider/queries/tree-sitter-language-pack/rust-tags.scm +60 -0
- aider/queries/tree-sitter-language-pack/solidity-tags.scm +43 -0
- aider/queries/tree-sitter-language-pack/swift-tags.scm +51 -0
- aider/queries/tree-sitter-language-pack/udev-tags.scm +20 -0
- aider/queries/tree-sitter-languages/README.md +24 -0
- aider/queries/tree-sitter-languages/c-tags.scm +9 -0
- aider/queries/tree-sitter-languages/c_sharp-tags.scm +46 -0
- aider/queries/tree-sitter-languages/cpp-tags.scm +15 -0
- aider/queries/tree-sitter-languages/dart-tags.scm +91 -0
- aider/queries/tree-sitter-languages/elisp-tags.scm +8 -0
- aider/queries/tree-sitter-languages/elixir-tags.scm +54 -0
- aider/queries/tree-sitter-languages/elm-tags.scm +19 -0
- aider/queries/tree-sitter-languages/fortran-tags.scm +15 -0
- aider/queries/tree-sitter-languages/go-tags.scm +30 -0
- aider/queries/tree-sitter-languages/haskell-tags.scm +3 -0
- aider/queries/tree-sitter-languages/hcl-tags.scm +77 -0
- aider/queries/tree-sitter-languages/java-tags.scm +20 -0
- aider/queries/tree-sitter-languages/javascript-tags.scm +88 -0
- aider/queries/tree-sitter-languages/julia-tags.scm +60 -0
- aider/queries/tree-sitter-languages/kotlin-tags.scm +27 -0
- aider/queries/tree-sitter-languages/matlab-tags.scm +10 -0
- aider/queries/tree-sitter-languages/ocaml-tags.scm +115 -0
- aider/queries/tree-sitter-languages/ocaml_interface-tags.scm +98 -0
- aider/queries/tree-sitter-languages/php-tags.scm +26 -0
- aider/queries/tree-sitter-languages/python-tags.scm +12 -0
- aider/queries/tree-sitter-languages/ql-tags.scm +26 -0
- aider/queries/tree-sitter-languages/ruby-tags.scm +64 -0
- aider/queries/tree-sitter-languages/rust-tags.scm +60 -0
- aider/queries/tree-sitter-languages/scala-tags.scm +65 -0
- aider/queries/tree-sitter-languages/typescript-tags.scm +41 -0
- aider/queries/tree-sitter-languages/zig-tags.scm +3 -0
- aider/reasoning_tags.py +82 -0
- aider/repo.py +621 -0
- aider/repomap.py +1174 -0
- aider/report.py +260 -0
- aider/resources/__init__.py +3 -0
- aider/resources/model-metadata.json +776 -0
- aider/resources/model-settings.yml +2068 -0
- aider/run_cmd.py +133 -0
- aider/scrape.py +293 -0
- aider/sendchat.py +242 -0
- aider/sessions.py +256 -0
- aider/special.py +203 -0
- aider/tools/__init__.py +72 -0
- aider/tools/command.py +105 -0
- aider/tools/command_interactive.py +122 -0
- aider/tools/delete_block.py +182 -0
- aider/tools/delete_line.py +155 -0
- aider/tools/delete_lines.py +184 -0
- aider/tools/extract_lines.py +341 -0
- aider/tools/finished.py +48 -0
- aider/tools/git_branch.py +129 -0
- aider/tools/git_diff.py +60 -0
- aider/tools/git_log.py +57 -0
- aider/tools/git_remote.py +53 -0
- aider/tools/git_show.py +51 -0
- aider/tools/git_status.py +46 -0
- aider/tools/grep.py +256 -0
- aider/tools/indent_lines.py +221 -0
- aider/tools/insert_block.py +288 -0
- aider/tools/list_changes.py +86 -0
- aider/tools/ls.py +93 -0
- aider/tools/make_editable.py +85 -0
- aider/tools/make_readonly.py +69 -0
- aider/tools/remove.py +91 -0
- aider/tools/replace_all.py +126 -0
- aider/tools/replace_line.py +173 -0
- aider/tools/replace_lines.py +217 -0
- aider/tools/replace_text.py +187 -0
- aider/tools/show_numbered_context.py +147 -0
- aider/tools/tool_utils.py +313 -0
- aider/tools/undo_change.py +95 -0
- aider/tools/update_todo_list.py +156 -0
- aider/tools/view.py +57 -0
- aider/tools/view_files_matching.py +141 -0
- aider/tools/view_files_with_symbol.py +129 -0
- aider/urls.py +17 -0
- aider/utils.py +456 -0
- aider/versioncheck.py +113 -0
- aider/voice.py +205 -0
- aider/waiting.py +38 -0
- aider/watch.py +318 -0
- aider/watch_prompts.py +12 -0
- aider/website/Gemfile +8 -0
- aider/website/_includes/blame.md +162 -0
- aider/website/_includes/get-started.md +22 -0
- aider/website/_includes/help-tip.md +5 -0
- aider/website/_includes/help.md +24 -0
- aider/website/_includes/install.md +5 -0
- aider/website/_includes/keys.md +4 -0
- aider/website/_includes/model-warnings.md +67 -0
- aider/website/_includes/multi-line.md +22 -0
- aider/website/_includes/python-m-aider.md +5 -0
- aider/website/_includes/recording.css +228 -0
- aider/website/_includes/recording.md +34 -0
- aider/website/_includes/replit-pipx.md +9 -0
- aider/website/_includes/works-best.md +1 -0
- aider/website/_sass/custom/custom.scss +103 -0
- aider/website/docs/config/adv-model-settings.md +2261 -0
- aider/website/docs/config/agent-mode.md +194 -0
- aider/website/docs/config/aider_conf.md +548 -0
- aider/website/docs/config/api-keys.md +90 -0
- aider/website/docs/config/dotenv.md +493 -0
- aider/website/docs/config/editor.md +127 -0
- aider/website/docs/config/mcp.md +95 -0
- aider/website/docs/config/model-aliases.md +104 -0
- aider/website/docs/config/options.md +890 -0
- aider/website/docs/config/reasoning.md +210 -0
- aider/website/docs/config.md +44 -0
- aider/website/docs/faq.md +384 -0
- aider/website/docs/git.md +76 -0
- aider/website/docs/index.md +47 -0
- aider/website/docs/install/codespaces.md +39 -0
- aider/website/docs/install/docker.md +57 -0
- aider/website/docs/install/optional.md +100 -0
- aider/website/docs/install/replit.md +8 -0
- aider/website/docs/install.md +115 -0
- aider/website/docs/languages.md +264 -0
- aider/website/docs/legal/contributor-agreement.md +111 -0
- aider/website/docs/legal/privacy.md +104 -0
- aider/website/docs/llms/anthropic.md +77 -0
- aider/website/docs/llms/azure.md +48 -0
- aider/website/docs/llms/bedrock.md +132 -0
- aider/website/docs/llms/cohere.md +34 -0
- aider/website/docs/llms/deepseek.md +32 -0
- aider/website/docs/llms/gemini.md +49 -0
- aider/website/docs/llms/github.md +111 -0
- aider/website/docs/llms/groq.md +36 -0
- aider/website/docs/llms/lm-studio.md +39 -0
- aider/website/docs/llms/ollama.md +75 -0
- aider/website/docs/llms/openai-compat.md +39 -0
- aider/website/docs/llms/openai.md +58 -0
- aider/website/docs/llms/openrouter.md +78 -0
- aider/website/docs/llms/other.md +117 -0
- aider/website/docs/llms/vertex.md +50 -0
- aider/website/docs/llms/warnings.md +10 -0
- aider/website/docs/llms/xai.md +53 -0
- aider/website/docs/llms.md +54 -0
- aider/website/docs/more/analytics.md +127 -0
- aider/website/docs/more/edit-formats.md +116 -0
- aider/website/docs/more/infinite-output.md +165 -0
- aider/website/docs/more-info.md +8 -0
- aider/website/docs/recordings/auto-accept-architect.md +31 -0
- aider/website/docs/recordings/dont-drop-original-read-files.md +35 -0
- aider/website/docs/recordings/index.md +21 -0
- aider/website/docs/recordings/model-accepts-settings.md +69 -0
- aider/website/docs/recordings/tree-sitter-language-pack.md +80 -0
- aider/website/docs/repomap.md +112 -0
- aider/website/docs/scripting.md +100 -0
- aider/website/docs/sessions.md +203 -0
- aider/website/docs/troubleshooting/aider-not-found.md +24 -0
- aider/website/docs/troubleshooting/edit-errors.md +76 -0
- aider/website/docs/troubleshooting/imports.md +62 -0
- aider/website/docs/troubleshooting/models-and-keys.md +54 -0
- aider/website/docs/troubleshooting/support.md +79 -0
- aider/website/docs/troubleshooting/token-limits.md +96 -0
- aider/website/docs/troubleshooting/warnings.md +12 -0
- aider/website/docs/troubleshooting.md +11 -0
- aider/website/docs/usage/browser.md +57 -0
- aider/website/docs/usage/caching.md +49 -0
- aider/website/docs/usage/commands.md +133 -0
- aider/website/docs/usage/conventions.md +119 -0
- aider/website/docs/usage/copypaste.md +121 -0
- aider/website/docs/usage/images-urls.md +48 -0
- aider/website/docs/usage/lint-test.md +118 -0
- aider/website/docs/usage/modes.md +211 -0
- aider/website/docs/usage/not-code.md +179 -0
- aider/website/docs/usage/notifications.md +87 -0
- aider/website/docs/usage/tips.md +79 -0
- aider/website/docs/usage/tutorials.md +30 -0
- aider/website/docs/usage/voice.md +121 -0
- aider/website/docs/usage/watch.md +294 -0
- aider/website/docs/usage.md +102 -0
- aider/website/share/index.md +101 -0
- aider_ce-0.88.20.dist-info/METADATA +187 -0
- aider_ce-0.88.20.dist-info/RECORD +279 -0
- aider_ce-0.88.20.dist-info/WHEEL +5 -0
- aider_ce-0.88.20.dist-info/entry_points.txt +2 -0
- aider_ce-0.88.20.dist-info/licenses/LICENSE.txt +202 -0
- aider_ce-0.88.20.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
from .tool_utils import (
|
|
2
|
+
ToolError,
|
|
3
|
+
apply_change,
|
|
4
|
+
format_tool_result,
|
|
5
|
+
generate_unified_diff_snippet,
|
|
6
|
+
handle_tool_error,
|
|
7
|
+
validate_file_for_edit,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
schema = {
|
|
11
|
+
"type": "function",
|
|
12
|
+
"function": {
|
|
13
|
+
"name": "ReplaceText",
|
|
14
|
+
"description": "Replace text in a file.",
|
|
15
|
+
"parameters": {
|
|
16
|
+
"type": "object",
|
|
17
|
+
"properties": {
|
|
18
|
+
"file_path": {"type": "string"},
|
|
19
|
+
"find_text": {"type": "string"},
|
|
20
|
+
"replace_text": {"type": "string"},
|
|
21
|
+
"near_context": {"type": "string"},
|
|
22
|
+
"occurrence": {"type": "integer", "default": 1},
|
|
23
|
+
"change_id": {"type": "string"},
|
|
24
|
+
"dry_run": {"type": "boolean", "default": False},
|
|
25
|
+
},
|
|
26
|
+
"required": ["file_path", "find_text", "replace_text"],
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
# Normalized tool name for lookup
|
|
32
|
+
NORM_NAME = "replacetext"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _execute_replace_text(
|
|
36
|
+
coder,
|
|
37
|
+
file_path,
|
|
38
|
+
find_text,
|
|
39
|
+
replace_text,
|
|
40
|
+
near_context=None,
|
|
41
|
+
occurrence=1,
|
|
42
|
+
change_id=None,
|
|
43
|
+
dry_run=False,
|
|
44
|
+
):
|
|
45
|
+
"""
|
|
46
|
+
Replace specific text with new text, optionally using nearby context for disambiguation.
|
|
47
|
+
Uses utility functions for validation, finding occurrences, and applying changes.
|
|
48
|
+
"""
|
|
49
|
+
tool_name = "ReplaceText"
|
|
50
|
+
try:
|
|
51
|
+
# 1. Validate file and get content
|
|
52
|
+
abs_path, rel_path, original_content = validate_file_for_edit(coder, file_path)
|
|
53
|
+
|
|
54
|
+
# 2. Find occurrences using helper function
|
|
55
|
+
# Note: _find_occurrences is currently on the Coder class, not in tool_utils
|
|
56
|
+
occurrences = coder._find_occurrences(original_content, find_text, near_context)
|
|
57
|
+
|
|
58
|
+
if not occurrences:
|
|
59
|
+
err_msg = f"Text '{find_text}' not found"
|
|
60
|
+
if near_context:
|
|
61
|
+
err_msg += f" near context '{near_context}'"
|
|
62
|
+
err_msg += f" in file '{file_path}'."
|
|
63
|
+
raise ToolError(err_msg)
|
|
64
|
+
|
|
65
|
+
# 3. Select the occurrence index
|
|
66
|
+
num_occurrences = len(occurrences)
|
|
67
|
+
try:
|
|
68
|
+
occurrence = int(occurrence)
|
|
69
|
+
if occurrence == -1:
|
|
70
|
+
if num_occurrences == 0:
|
|
71
|
+
raise ToolError(f"Text '{find_text}' not found, cannot select last occurrence.")
|
|
72
|
+
target_idx = num_occurrences - 1
|
|
73
|
+
elif 1 <= occurrence <= num_occurrences:
|
|
74
|
+
target_idx = occurrence - 1 # Convert 1-based to 0-based
|
|
75
|
+
else:
|
|
76
|
+
err_msg = (
|
|
77
|
+
f"Occurrence number {occurrence} is out of range. Found"
|
|
78
|
+
f" {num_occurrences} occurrences of '{find_text}'"
|
|
79
|
+
)
|
|
80
|
+
if near_context:
|
|
81
|
+
err_msg += f" near '{near_context}'"
|
|
82
|
+
err_msg += f" in '{file_path}'."
|
|
83
|
+
raise ToolError(err_msg)
|
|
84
|
+
except ValueError:
|
|
85
|
+
raise ToolError(f"Invalid occurrence value: '{occurrence}'. Must be an integer.")
|
|
86
|
+
|
|
87
|
+
start_index = occurrences[target_idx]
|
|
88
|
+
|
|
89
|
+
# 4. Perform the replacement
|
|
90
|
+
new_content = (
|
|
91
|
+
original_content[:start_index]
|
|
92
|
+
+ replace_text
|
|
93
|
+
+ original_content[start_index + len(find_text) :]
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
if original_content == new_content:
|
|
97
|
+
coder.io.tool_warning("No changes made: replacement text is identical to original")
|
|
98
|
+
return "Warning: No changes made (replacement identical to original)"
|
|
99
|
+
|
|
100
|
+
# 5. Generate diff for feedback
|
|
101
|
+
# Note: _generate_diff_snippet is currently on the Coder class
|
|
102
|
+
diff_snippet = generate_unified_diff_snippet(original_content, new_content, rel_path)
|
|
103
|
+
occurrence_str = f"occurrence {occurrence}" if num_occurrences > 1 else "text"
|
|
104
|
+
|
|
105
|
+
# 6. Handle dry run
|
|
106
|
+
if dry_run:
|
|
107
|
+
dry_run_message = (
|
|
108
|
+
f"Dry run: Would replace {occurrence_str} of '{find_text}' in {file_path}."
|
|
109
|
+
)
|
|
110
|
+
return format_tool_result(
|
|
111
|
+
coder,
|
|
112
|
+
tool_name,
|
|
113
|
+
"",
|
|
114
|
+
dry_run=True,
|
|
115
|
+
dry_run_message=dry_run_message,
|
|
116
|
+
diff_snippet=diff_snippet,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
# 7. Apply Change (Not dry run)
|
|
120
|
+
metadata = {
|
|
121
|
+
"start_index": start_index,
|
|
122
|
+
"find_text": find_text,
|
|
123
|
+
"replace_text": replace_text,
|
|
124
|
+
"near_context": near_context,
|
|
125
|
+
"occurrence": occurrence,
|
|
126
|
+
}
|
|
127
|
+
final_change_id = apply_change(
|
|
128
|
+
coder,
|
|
129
|
+
abs_path,
|
|
130
|
+
rel_path,
|
|
131
|
+
original_content,
|
|
132
|
+
new_content,
|
|
133
|
+
"replacetext",
|
|
134
|
+
metadata,
|
|
135
|
+
change_id,
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
coder.files_edited_by_tools.add(rel_path)
|
|
139
|
+
# 8. Format and return result
|
|
140
|
+
success_message = f"Replaced {occurrence_str} in {file_path}"
|
|
141
|
+
return format_tool_result(
|
|
142
|
+
coder, tool_name, success_message, change_id=final_change_id, diff_snippet=diff_snippet
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
except ToolError as e:
|
|
146
|
+
# Handle errors raised by utility functions or explicitly raised here
|
|
147
|
+
return handle_tool_error(coder, tool_name, e, add_traceback=False)
|
|
148
|
+
except Exception as e:
|
|
149
|
+
# Handle unexpected errors
|
|
150
|
+
return handle_tool_error(coder, tool_name, e)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def process_response(coder, params):
|
|
154
|
+
"""
|
|
155
|
+
Process the ReplaceText tool response.
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
coder: The Coder instance
|
|
159
|
+
params: Dictionary of parameters
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
str: Result message
|
|
163
|
+
"""
|
|
164
|
+
file_path = params.get("file_path")
|
|
165
|
+
find_text = params.get("find_text")
|
|
166
|
+
replace_text = params.get("replace_text")
|
|
167
|
+
near_context = params.get("near_context")
|
|
168
|
+
occurrence = params.get("occurrence", 1)
|
|
169
|
+
change_id = params.get("change_id")
|
|
170
|
+
dry_run = params.get("dry_run", False)
|
|
171
|
+
|
|
172
|
+
if file_path is not None and find_text is not None and replace_text is not None:
|
|
173
|
+
return _execute_replace_text(
|
|
174
|
+
coder,
|
|
175
|
+
file_path,
|
|
176
|
+
find_text,
|
|
177
|
+
replace_text,
|
|
178
|
+
near_context,
|
|
179
|
+
occurrence,
|
|
180
|
+
change_id,
|
|
181
|
+
dry_run,
|
|
182
|
+
)
|
|
183
|
+
else:
|
|
184
|
+
return (
|
|
185
|
+
"Error: Missing required parameters for ReplaceText (file_path,"
|
|
186
|
+
" find_text, replace_text)"
|
|
187
|
+
)
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
from .tool_utils import ToolError, handle_tool_error, resolve_paths
|
|
4
|
+
|
|
5
|
+
schema = {
|
|
6
|
+
"type": "function",
|
|
7
|
+
"function": {
|
|
8
|
+
"name": "ShowNumberedContext",
|
|
9
|
+
"description": "Show numbered lines of context around a pattern or line number.",
|
|
10
|
+
"parameters": {
|
|
11
|
+
"type": "object",
|
|
12
|
+
"properties": {
|
|
13
|
+
"file_path": {"type": "string"},
|
|
14
|
+
"pattern": {"type": "string"},
|
|
15
|
+
"line_number": {"type": "integer"},
|
|
16
|
+
"context_lines": {"type": "integer", "default": 3},
|
|
17
|
+
},
|
|
18
|
+
"required": ["file_path"],
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
# Normalized tool name for lookup
|
|
24
|
+
NORM_NAME = "shownumberedcontext"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def execute_show_numbered_context(
|
|
28
|
+
coder, file_path, pattern=None, line_number=None, context_lines=3
|
|
29
|
+
):
|
|
30
|
+
"""
|
|
31
|
+
Displays numbered lines from file_path centered around a target location
|
|
32
|
+
(pattern or line_number), without adding the file to context.
|
|
33
|
+
Uses utility functions for path resolution and error handling.
|
|
34
|
+
"""
|
|
35
|
+
tool_name = "ShowNumberedContext"
|
|
36
|
+
try:
|
|
37
|
+
# 1. Validate arguments
|
|
38
|
+
if not (pattern is None) ^ (line_number is None):
|
|
39
|
+
raise ToolError("Provide exactly one of 'pattern' or 'line_number'.")
|
|
40
|
+
|
|
41
|
+
# 2. Resolve path
|
|
42
|
+
abs_path, rel_path = resolve_paths(coder, file_path)
|
|
43
|
+
if not os.path.exists(abs_path):
|
|
44
|
+
# Check existence after resolving, as resolve_paths doesn't guarantee existence
|
|
45
|
+
raise ToolError(f"File not found: {file_path}")
|
|
46
|
+
|
|
47
|
+
# 3. Read file content
|
|
48
|
+
content = coder.io.read_text(abs_path)
|
|
49
|
+
if content is None:
|
|
50
|
+
raise ToolError(f"Could not read file: {file_path}")
|
|
51
|
+
lines = content.splitlines()
|
|
52
|
+
num_lines = len(lines)
|
|
53
|
+
|
|
54
|
+
# 4. Determine center line index
|
|
55
|
+
center_line_idx = -1
|
|
56
|
+
found_by = ""
|
|
57
|
+
|
|
58
|
+
if line_number is not None:
|
|
59
|
+
try:
|
|
60
|
+
line_number_int = int(line_number)
|
|
61
|
+
if 1 <= line_number_int <= num_lines:
|
|
62
|
+
center_line_idx = line_number_int - 1 # Convert to 0-based index
|
|
63
|
+
found_by = f"line {line_number_int}"
|
|
64
|
+
else:
|
|
65
|
+
raise ToolError(
|
|
66
|
+
f"Line number {line_number_int} is out of range (1-{num_lines}) for"
|
|
67
|
+
f" {file_path}."
|
|
68
|
+
)
|
|
69
|
+
except ValueError:
|
|
70
|
+
raise ToolError(f"Invalid line number '{line_number}'. Must be an integer.")
|
|
71
|
+
|
|
72
|
+
elif pattern is not None:
|
|
73
|
+
# TODO: Update this section for multiline pattern support later
|
|
74
|
+
first_match_line_idx = -1
|
|
75
|
+
for i, line in enumerate(lines):
|
|
76
|
+
if pattern in line:
|
|
77
|
+
first_match_line_idx = i
|
|
78
|
+
break
|
|
79
|
+
|
|
80
|
+
if first_match_line_idx != -1:
|
|
81
|
+
center_line_idx = first_match_line_idx
|
|
82
|
+
found_by = f"pattern '{pattern}' on line {center_line_idx + 1}"
|
|
83
|
+
else:
|
|
84
|
+
raise ToolError(f"Pattern '{pattern}' not found in {file_path}.")
|
|
85
|
+
|
|
86
|
+
if center_line_idx == -1:
|
|
87
|
+
# Should not happen if logic above is correct, but as a safeguard
|
|
88
|
+
raise ToolError("Internal error: Could not determine center line.")
|
|
89
|
+
|
|
90
|
+
# 5. Calculate context window
|
|
91
|
+
try:
|
|
92
|
+
context_lines_int = int(context_lines)
|
|
93
|
+
if context_lines_int < 0:
|
|
94
|
+
raise ValueError("Context lines must be non-negative")
|
|
95
|
+
except ValueError:
|
|
96
|
+
coder.io.tool_warning(
|
|
97
|
+
f"Invalid context_lines value '{context_lines}', using default 3."
|
|
98
|
+
)
|
|
99
|
+
context_lines_int = 3
|
|
100
|
+
|
|
101
|
+
start_line_idx = max(0, center_line_idx - context_lines_int)
|
|
102
|
+
end_line_idx = min(num_lines - 1, center_line_idx + context_lines_int)
|
|
103
|
+
|
|
104
|
+
# 6. Format output
|
|
105
|
+
# Use rel_path for user-facing messages
|
|
106
|
+
output_lines = [f"Displaying context around {found_by} in {rel_path}:"]
|
|
107
|
+
max_line_num_width = len(str(end_line_idx + 1)) # Width for padding
|
|
108
|
+
|
|
109
|
+
for i in range(start_line_idx, end_line_idx + 1):
|
|
110
|
+
line_num_str = str(i + 1).rjust(max_line_num_width)
|
|
111
|
+
output_lines.append(f"{line_num_str} | {lines[i]}")
|
|
112
|
+
|
|
113
|
+
# Log success and return the formatted context directly
|
|
114
|
+
coder.io.tool_output(f"Successfully retrieved context for {rel_path}")
|
|
115
|
+
return "\n".join(output_lines)
|
|
116
|
+
|
|
117
|
+
except ToolError as e:
|
|
118
|
+
# Handle expected errors raised by utility functions or validation
|
|
119
|
+
return handle_tool_error(coder, tool_name, e, add_traceback=False)
|
|
120
|
+
except Exception as e:
|
|
121
|
+
# Handle unexpected errors during processing
|
|
122
|
+
return handle_tool_error(coder, tool_name, e)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def process_response(coder, params):
|
|
126
|
+
"""
|
|
127
|
+
Process the ShowNumberedContext tool response.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
coder: The Coder instance
|
|
131
|
+
params: Dictionary of parameters
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
str: Result message
|
|
135
|
+
"""
|
|
136
|
+
file_path = params.get("file_path")
|
|
137
|
+
pattern = params.get("pattern")
|
|
138
|
+
line_number = params.get("line_number")
|
|
139
|
+
context_lines = params.get("context_lines", 3)
|
|
140
|
+
|
|
141
|
+
if file_path is not None and (pattern is not None or line_number is not None):
|
|
142
|
+
return execute_show_numbered_context(coder, file_path, pattern, line_number, context_lines)
|
|
143
|
+
else:
|
|
144
|
+
return (
|
|
145
|
+
"Error: Missing required parameters for ViewNumberedContext (file_path"
|
|
146
|
+
" and either pattern or line_number)"
|
|
147
|
+
)
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
import difflib
|
|
2
|
+
import os
|
|
3
|
+
import re
|
|
4
|
+
import traceback
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ToolError(Exception):
|
|
8
|
+
"""Custom exception for tool-specific errors that should be reported to the LLM."""
|
|
9
|
+
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def resolve_paths(coder, file_path):
|
|
14
|
+
"""Resolves absolute and relative paths for a given file path."""
|
|
15
|
+
try:
|
|
16
|
+
abs_path = coder.abs_root_path(file_path)
|
|
17
|
+
rel_path = coder.get_rel_fname(abs_path)
|
|
18
|
+
return abs_path, rel_path
|
|
19
|
+
except Exception as e:
|
|
20
|
+
# Wrap unexpected errors during path resolution
|
|
21
|
+
raise ToolError(f"Error resolving path '{file_path}': {e}")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def validate_file_for_edit(coder, file_path):
|
|
25
|
+
"""
|
|
26
|
+
Validates if a file exists, is in context, and is editable.
|
|
27
|
+
Reads and returns original content if valid.
|
|
28
|
+
Raises ToolError on failure.
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
tuple: (absolute_path, relative_path, original_content)
|
|
32
|
+
"""
|
|
33
|
+
abs_path, rel_path = resolve_paths(coder, file_path)
|
|
34
|
+
|
|
35
|
+
if not os.path.isfile(abs_path):
|
|
36
|
+
raise ToolError(f"File '{file_path}' not found")
|
|
37
|
+
|
|
38
|
+
if abs_path not in coder.abs_fnames:
|
|
39
|
+
if abs_path in coder.abs_read_only_fnames:
|
|
40
|
+
raise ToolError(f"File '{file_path}' is read-only. Use MakeEditable first.")
|
|
41
|
+
else:
|
|
42
|
+
# File exists but is not in context at all
|
|
43
|
+
raise ToolError(f"File '{file_path}' not in context. Use View or MakeEditable first.")
|
|
44
|
+
|
|
45
|
+
# Reread content immediately before potential modification
|
|
46
|
+
content = coder.io.read_text(abs_path)
|
|
47
|
+
if content is None:
|
|
48
|
+
# This indicates an issue reading a file we know exists and is in context
|
|
49
|
+
coder.io.tool_error(
|
|
50
|
+
f"Internal error: Could not read file '{file_path}' which should be accessible."
|
|
51
|
+
)
|
|
52
|
+
raise ToolError(f"Could not read file '{file_path}'")
|
|
53
|
+
|
|
54
|
+
return abs_path, rel_path, content
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def find_pattern_indices(lines, pattern, use_regex=False):
|
|
58
|
+
"""Finds all line indices matching a pattern."""
|
|
59
|
+
indices = []
|
|
60
|
+
for i, line in enumerate(lines):
|
|
61
|
+
if (use_regex and re.search(pattern, line)) or (not use_regex and pattern in line):
|
|
62
|
+
indices.append(i)
|
|
63
|
+
return indices
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def select_occurrence_index(indices, occurrence, pattern_desc="Pattern"):
|
|
67
|
+
"""
|
|
68
|
+
Selects the target 0-based index from a list of indices based on the 1-based occurrence parameter.
|
|
69
|
+
Raises ToolError if the pattern wasn't found or the occurrence is invalid.
|
|
70
|
+
"""
|
|
71
|
+
num_occurrences = len(indices)
|
|
72
|
+
if not indices:
|
|
73
|
+
raise ToolError(f"{pattern_desc} not found")
|
|
74
|
+
|
|
75
|
+
try:
|
|
76
|
+
occurrence = int(occurrence) # Ensure occurrence is an integer
|
|
77
|
+
if occurrence == -1: # Last occurrence
|
|
78
|
+
if num_occurrences == 0:
|
|
79
|
+
raise ToolError(f"{pattern_desc} not found, cannot select last occurrence.")
|
|
80
|
+
target_idx = num_occurrences - 1
|
|
81
|
+
elif 1 <= occurrence <= num_occurrences:
|
|
82
|
+
target_idx = occurrence - 1 # Convert 1-based to 0-based
|
|
83
|
+
else:
|
|
84
|
+
raise ToolError(
|
|
85
|
+
f"Occurrence number {occurrence} is out of range for {pattern_desc}. Found"
|
|
86
|
+
f" {num_occurrences} occurrences."
|
|
87
|
+
)
|
|
88
|
+
except ValueError:
|
|
89
|
+
raise ToolError(f"Invalid occurrence value: '{occurrence}'. Must be an integer.")
|
|
90
|
+
|
|
91
|
+
return indices[target_idx]
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def determine_line_range(
|
|
95
|
+
coder,
|
|
96
|
+
file_path,
|
|
97
|
+
lines,
|
|
98
|
+
start_pattern_line_index=None, # Made optional
|
|
99
|
+
end_pattern=None,
|
|
100
|
+
line_count=None,
|
|
101
|
+
target_symbol=None,
|
|
102
|
+
pattern_desc="Block",
|
|
103
|
+
):
|
|
104
|
+
"""
|
|
105
|
+
Determines the end line index based on end_pattern or line_count.
|
|
106
|
+
Raises ToolError if end_pattern is not found or line_count is invalid.
|
|
107
|
+
"""
|
|
108
|
+
# Parameter validation: Ensure only one targeting method is used
|
|
109
|
+
targeting_methods = [
|
|
110
|
+
target_symbol is not None,
|
|
111
|
+
start_pattern_line_index is not None,
|
|
112
|
+
# Note: line_count and end_pattern depend on start_pattern_line_index
|
|
113
|
+
]
|
|
114
|
+
if sum(targeting_methods) > 1:
|
|
115
|
+
raise ToolError("Cannot specify target_symbol along with start_pattern.")
|
|
116
|
+
if sum(targeting_methods) == 0:
|
|
117
|
+
raise ToolError(
|
|
118
|
+
"Must specify either target_symbol or start_pattern."
|
|
119
|
+
) # Or line numbers for line-based tools, handled elsewhere
|
|
120
|
+
|
|
121
|
+
if target_symbol:
|
|
122
|
+
if end_pattern or line_count:
|
|
123
|
+
raise ToolError("Cannot specify end_pattern or line_count when using target_symbol.")
|
|
124
|
+
try:
|
|
125
|
+
# Use repo_map to find the symbol's definition range
|
|
126
|
+
start_line, end_line = coder.repo_map.get_symbol_definition_location(
|
|
127
|
+
file_path, target_symbol
|
|
128
|
+
)
|
|
129
|
+
return start_line, end_line
|
|
130
|
+
except AttributeError: # Use specific exception
|
|
131
|
+
# Check if repo_map exists and is initialized before accessing methods
|
|
132
|
+
if not hasattr(coder, "repo_map") or coder.repo_map is None:
|
|
133
|
+
raise ToolError("RepoMap is not available or not initialized.")
|
|
134
|
+
# If repo_map exists, the error might be from get_symbol_definition_location itself
|
|
135
|
+
# Re-raise ToolErrors directly
|
|
136
|
+
raise
|
|
137
|
+
except ToolError as e:
|
|
138
|
+
# Propagate specific ToolErrors from repo_map (not found, ambiguous, etc.)
|
|
139
|
+
raise e
|
|
140
|
+
except Exception as e:
|
|
141
|
+
# Catch other unexpected errors during symbol lookup
|
|
142
|
+
raise ToolError(f"Unexpected error looking up symbol '{target_symbol}': {e}")
|
|
143
|
+
|
|
144
|
+
# --- Existing logic for pattern/line_count based targeting ---
|
|
145
|
+
# Ensure start_pattern_line_index is provided if not using target_symbol
|
|
146
|
+
if start_pattern_line_index is None:
|
|
147
|
+
raise ToolError(
|
|
148
|
+
"Internal error: start_pattern_line_index is required when not using target_symbol."
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
# Assign start_line here for the pattern-based logic path
|
|
152
|
+
start_line = start_pattern_line_index # Start of existing logic
|
|
153
|
+
start_line = start_pattern_line_index
|
|
154
|
+
end_line = -1
|
|
155
|
+
|
|
156
|
+
if end_pattern and line_count:
|
|
157
|
+
raise ToolError("Cannot specify both end_pattern and line_count")
|
|
158
|
+
|
|
159
|
+
if end_pattern:
|
|
160
|
+
found_end = False
|
|
161
|
+
# Search from the start_line onwards for the end_pattern
|
|
162
|
+
for i in range(start_line, len(lines)):
|
|
163
|
+
if end_pattern in lines[i]:
|
|
164
|
+
end_line = i
|
|
165
|
+
found_end = True
|
|
166
|
+
break
|
|
167
|
+
if not found_end:
|
|
168
|
+
raise ToolError(
|
|
169
|
+
f"End pattern '{end_pattern}' not found after start pattern on line"
|
|
170
|
+
f" {start_line + 1}"
|
|
171
|
+
)
|
|
172
|
+
elif line_count:
|
|
173
|
+
try:
|
|
174
|
+
line_count = int(line_count)
|
|
175
|
+
if line_count <= 0:
|
|
176
|
+
raise ValueError("Line count must be positive")
|
|
177
|
+
# Calculate end line index, ensuring it doesn't exceed file bounds
|
|
178
|
+
end_line = min(start_line + line_count - 1, len(lines) - 1)
|
|
179
|
+
except ValueError:
|
|
180
|
+
raise ToolError(
|
|
181
|
+
f"Invalid line_count value: '{line_count}'. Must be a positive integer."
|
|
182
|
+
)
|
|
183
|
+
else:
|
|
184
|
+
# If neither end_pattern nor line_count is given, the range is just the start line
|
|
185
|
+
end_line = start_line
|
|
186
|
+
|
|
187
|
+
return start_line, end_line
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def generate_unified_diff_snippet(original_content, new_content, file_path, context_lines=3):
|
|
191
|
+
"""
|
|
192
|
+
Generates a unified diff snippet between original and new content.
|
|
193
|
+
|
|
194
|
+
Args:
|
|
195
|
+
original_content (str): The original file content.
|
|
196
|
+
new_content (str): The modified file content.
|
|
197
|
+
file_path (str): The relative path to the file (for display in diff header).
|
|
198
|
+
context_lines (int): Number of context lines to show around changes.
|
|
199
|
+
|
|
200
|
+
Returns:
|
|
201
|
+
str: A formatted unified diff snippet, or an empty string if no changes.
|
|
202
|
+
"""
|
|
203
|
+
if original_content == new_content:
|
|
204
|
+
return ""
|
|
205
|
+
|
|
206
|
+
original_lines = original_content.splitlines(keepends=True)
|
|
207
|
+
new_lines = new_content.splitlines(keepends=True)
|
|
208
|
+
|
|
209
|
+
diff = difflib.unified_diff(
|
|
210
|
+
original_lines,
|
|
211
|
+
new_lines,
|
|
212
|
+
fromfile=f"a/{file_path}",
|
|
213
|
+
tofile=f"b/{file_path}",
|
|
214
|
+
n=context_lines, # Number of context lines
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
# Join the diff lines, potentially skipping the header if desired,
|
|
218
|
+
# but let's keep it for standard format.
|
|
219
|
+
diff_snippet = "".join(diff)
|
|
220
|
+
|
|
221
|
+
# Ensure snippet ends with a newline for cleaner formatting in results
|
|
222
|
+
if diff_snippet and not diff_snippet.endswith("\n"):
|
|
223
|
+
diff_snippet += "\n"
|
|
224
|
+
|
|
225
|
+
return diff_snippet
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def apply_change(
|
|
229
|
+
coder, abs_path, rel_path, original_content, new_content, change_type, metadata, change_id=None
|
|
230
|
+
):
|
|
231
|
+
"""
|
|
232
|
+
Writes the new content, tracks the change, and updates coder state.
|
|
233
|
+
Returns the final change ID. Raises ToolError on tracking failure.
|
|
234
|
+
"""
|
|
235
|
+
coder.io.write_text(abs_path, new_content)
|
|
236
|
+
try:
|
|
237
|
+
final_change_id = coder.change_tracker.track_change(
|
|
238
|
+
file_path=rel_path,
|
|
239
|
+
change_type=change_type,
|
|
240
|
+
original_content=original_content,
|
|
241
|
+
new_content=new_content,
|
|
242
|
+
metadata=metadata,
|
|
243
|
+
change_id=change_id,
|
|
244
|
+
)
|
|
245
|
+
except Exception as track_e:
|
|
246
|
+
# Log the error but also raise ToolError to inform the LLM
|
|
247
|
+
coder.io.tool_error(f"Error tracking change for {change_type}: {track_e}")
|
|
248
|
+
raise ToolError(f"Failed to track change: {track_e}")
|
|
249
|
+
|
|
250
|
+
coder.aider_edited_files.add(rel_path)
|
|
251
|
+
return final_change_id
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def handle_tool_error(coder, tool_name, e, add_traceback=True):
|
|
255
|
+
"""Logs tool errors and returns a formatted error message for the LLM."""
|
|
256
|
+
error_message = f"Error in {tool_name}: {str(e)}"
|
|
257
|
+
if add_traceback:
|
|
258
|
+
error_message += f"\n{traceback.format_exc()}"
|
|
259
|
+
coder.io.tool_error(error_message)
|
|
260
|
+
# Return only the core error message to the LLM for brevity
|
|
261
|
+
return f"Error: {str(e)}"
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def format_tool_result(
|
|
265
|
+
coder,
|
|
266
|
+
tool_name,
|
|
267
|
+
success_message,
|
|
268
|
+
change_id=None,
|
|
269
|
+
diff_snippet=None,
|
|
270
|
+
dry_run=False,
|
|
271
|
+
dry_run_message=None,
|
|
272
|
+
):
|
|
273
|
+
"""Formats the result message for tool execution."""
|
|
274
|
+
if dry_run:
|
|
275
|
+
full_message = dry_run_message or f"Dry run: Would execute {tool_name}."
|
|
276
|
+
if diff_snippet:
|
|
277
|
+
full_message += f" Diff snippet:\n{diff_snippet}"
|
|
278
|
+
coder.io.tool_output(full_message) # Log the dry run action
|
|
279
|
+
return full_message
|
|
280
|
+
else:
|
|
281
|
+
# Use the provided success message, potentially adding change_id and diff
|
|
282
|
+
full_message = f"✅ {success_message}"
|
|
283
|
+
if change_id:
|
|
284
|
+
full_message += f" (change_id: {change_id})"
|
|
285
|
+
coder.io.tool_output(full_message) # Log the success action
|
|
286
|
+
|
|
287
|
+
result_for_llm = f"Successfully executed {tool_name}."
|
|
288
|
+
if change_id:
|
|
289
|
+
result_for_llm += f" Change ID: {change_id}."
|
|
290
|
+
if diff_snippet:
|
|
291
|
+
result_for_llm += f" Diff snippet:\n{diff_snippet}"
|
|
292
|
+
return result_for_llm
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
# Example usage within a hypothetical tool:
|
|
296
|
+
# try:
|
|
297
|
+
# abs_path, rel_path, original_content = validate_file_for_edit(coder, file_path)
|
|
298
|
+
# # ... tool specific logic to determine new_content and metadata ...
|
|
299
|
+
# if dry_run:
|
|
300
|
+
# return format_tool_result(coder, "MyTool", "", dry_run=True, diff_snippet=diff)
|
|
301
|
+
#
|
|
302
|
+
# change_id = apply_change(coder, abs_path, rel_path, original_content, new_content, 'mytool', metadata)
|
|
303
|
+
# return format_tool_result(
|
|
304
|
+
# coder,
|
|
305
|
+
# "MyTool",
|
|
306
|
+
# f"Applied change to {file_path}",
|
|
307
|
+
# change_id=change_id,
|
|
308
|
+
# diff_snippet=diff
|
|
309
|
+
# )
|
|
310
|
+
# except ToolError as e:
|
|
311
|
+
# return handle_tool_error(coder, "MyTool", e, add_traceback=False) # Don't need traceback for ToolErrors
|
|
312
|
+
# except Exception as e:
|
|
313
|
+
# return handle_tool_error(coder, "MyTool", e)
|