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
aider/tools/remove.py
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import time
|
|
2
|
+
|
|
3
|
+
schema = {
|
|
4
|
+
"type": "function",
|
|
5
|
+
"function": {
|
|
6
|
+
"name": "Remove",
|
|
7
|
+
"description": (
|
|
8
|
+
"Remove a file from the chat context. Should be used proactively to keep con"
|
|
9
|
+
"Should be used after editing a file when all edits are done "
|
|
10
|
+
"and the file is no longer necessary in context."
|
|
11
|
+
),
|
|
12
|
+
"parameters": {
|
|
13
|
+
"type": "object",
|
|
14
|
+
"properties": {
|
|
15
|
+
"file_path": {
|
|
16
|
+
"type": "string",
|
|
17
|
+
"description": "The path to the file to remove.",
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
"required": ["file_path"],
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
# Normalized tool name for lookup
|
|
26
|
+
NORM_NAME = "remove"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _execute_remove(coder, file_path):
|
|
30
|
+
"""
|
|
31
|
+
Explicitly remove a file from context.
|
|
32
|
+
|
|
33
|
+
This allows the LLM to clean up its context when files are no
|
|
34
|
+
longer needed, keeping the context focused and efficient.
|
|
35
|
+
"""
|
|
36
|
+
try:
|
|
37
|
+
# Get absolute path
|
|
38
|
+
abs_path = coder.abs_root_path(file_path)
|
|
39
|
+
rel_path = coder.get_rel_fname(abs_path)
|
|
40
|
+
|
|
41
|
+
# Check if file is in context (either editable or read-only)
|
|
42
|
+
removed = False
|
|
43
|
+
if abs_path in coder.abs_fnames:
|
|
44
|
+
# Don't remove if it's the last editable file and there are no read-only files
|
|
45
|
+
if len(coder.abs_fnames) <= 1 and not coder.abs_read_only_fnames:
|
|
46
|
+
coder.io.tool_output(
|
|
47
|
+
f"⚠️ Cannot remove '{file_path}' - it's the only file in context"
|
|
48
|
+
)
|
|
49
|
+
return "Cannot remove - last file in context"
|
|
50
|
+
coder.abs_fnames.remove(abs_path)
|
|
51
|
+
removed = True
|
|
52
|
+
elif abs_path in coder.abs_read_only_fnames:
|
|
53
|
+
# Don't remove if it's the last read-only file and there are no editable files
|
|
54
|
+
if len(coder.abs_read_only_fnames) <= 1 and not coder.abs_fnames:
|
|
55
|
+
coder.io.tool_output(
|
|
56
|
+
f"⚠️ Cannot remove '{file_path}' - it's the only file in context"
|
|
57
|
+
)
|
|
58
|
+
return "Cannot remove - last file in context"
|
|
59
|
+
coder.abs_read_only_fnames.remove(abs_path)
|
|
60
|
+
removed = True
|
|
61
|
+
|
|
62
|
+
if not removed:
|
|
63
|
+
coder.io.tool_output(f"⚠️ File '{file_path}' not in context")
|
|
64
|
+
return "File not in context"
|
|
65
|
+
|
|
66
|
+
# Track in recently removed
|
|
67
|
+
coder.recently_removed[rel_path] = {"removed_at": time.time()}
|
|
68
|
+
|
|
69
|
+
coder.io.tool_output(f"🗑️ Explicitly removed '{file_path}' from context")
|
|
70
|
+
return "Removed file from context"
|
|
71
|
+
except Exception as e:
|
|
72
|
+
coder.io.tool_error(f"Error removing file: {str(e)}")
|
|
73
|
+
return f"Error: {str(e)}"
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def process_response(coder, params):
|
|
77
|
+
"""
|
|
78
|
+
Process the Remove tool response.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
coder: The Coder instance
|
|
82
|
+
params: Dictionary of parameters
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
str: Result message
|
|
86
|
+
"""
|
|
87
|
+
file_path = params.get("file_path")
|
|
88
|
+
if file_path is not None:
|
|
89
|
+
return _execute_remove(coder, file_path)
|
|
90
|
+
else:
|
|
91
|
+
return "Error: Missing 'file_path' parameter for Remove"
|
|
@@ -0,0 +1,126 @@
|
|
|
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": "ReplaceAll",
|
|
14
|
+
"description": "Replace all occurrences of 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
|
+
"change_id": {"type": "string"},
|
|
22
|
+
"dry_run": {"type": "boolean", "default": False},
|
|
23
|
+
},
|
|
24
|
+
"required": ["file_path", "find_text", "replace_text"],
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
# Normalized tool name for lookup
|
|
30
|
+
NORM_NAME = "replaceall"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _execute_replace_all(coder, file_path, find_text, replace_text, change_id=None, dry_run=False):
|
|
34
|
+
"""
|
|
35
|
+
Replace all occurrences of text in a file using utility functions.
|
|
36
|
+
"""
|
|
37
|
+
# Get absolute file path
|
|
38
|
+
abs_path = coder.abs_root_path(file_path)
|
|
39
|
+
rel_path = coder.get_rel_fname(abs_path)
|
|
40
|
+
tool_name = "ReplaceAll"
|
|
41
|
+
try:
|
|
42
|
+
# 1. Validate file and get content
|
|
43
|
+
abs_path, rel_path, original_content = validate_file_for_edit(coder, file_path)
|
|
44
|
+
|
|
45
|
+
# 2. Count occurrences
|
|
46
|
+
count = original_content.count(find_text)
|
|
47
|
+
if count == 0:
|
|
48
|
+
coder.io.tool_warning(f"Text '{find_text}' not found in file '{file_path}'")
|
|
49
|
+
return "Warning: Text not found in file"
|
|
50
|
+
|
|
51
|
+
# 3. Perform the replacement
|
|
52
|
+
new_content = original_content.replace(find_text, replace_text)
|
|
53
|
+
|
|
54
|
+
if original_content == new_content:
|
|
55
|
+
coder.io.tool_warning("No changes made: replacement text is identical to original")
|
|
56
|
+
return "Warning: No changes made (replacement identical to original)"
|
|
57
|
+
|
|
58
|
+
# 4. Generate diff for feedback
|
|
59
|
+
diff_examples = generate_unified_diff_snippet(original_content, new_content, rel_path)
|
|
60
|
+
|
|
61
|
+
# 5. Handle dry run
|
|
62
|
+
if dry_run:
|
|
63
|
+
dry_run_message = (
|
|
64
|
+
f"Dry run: Would replace {count} occurrences of '{find_text}' in {file_path}."
|
|
65
|
+
)
|
|
66
|
+
return format_tool_result(
|
|
67
|
+
coder,
|
|
68
|
+
tool_name,
|
|
69
|
+
"",
|
|
70
|
+
dry_run=True,
|
|
71
|
+
dry_run_message=dry_run_message,
|
|
72
|
+
diff_snippet=diff_examples,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
# 6. Apply Change (Not dry run)
|
|
76
|
+
metadata = {"find_text": find_text, "replace_text": replace_text, "occurrences": count}
|
|
77
|
+
final_change_id = apply_change(
|
|
78
|
+
coder,
|
|
79
|
+
abs_path,
|
|
80
|
+
rel_path,
|
|
81
|
+
original_content,
|
|
82
|
+
new_content,
|
|
83
|
+
"replaceall",
|
|
84
|
+
metadata,
|
|
85
|
+
change_id,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
coder.files_edited_by_tools.add(rel_path)
|
|
89
|
+
|
|
90
|
+
# 7. Format and return result
|
|
91
|
+
success_message = f"Replaced {count} occurrences in {file_path}"
|
|
92
|
+
return format_tool_result(
|
|
93
|
+
coder, tool_name, success_message, change_id=final_change_id, diff_snippet=diff_examples
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
except ToolError as e:
|
|
97
|
+
# Handle errors raised by utility functions
|
|
98
|
+
return handle_tool_error(coder, tool_name, e, add_traceback=False)
|
|
99
|
+
except Exception as e:
|
|
100
|
+
# Handle unexpected errors
|
|
101
|
+
return handle_tool_error(coder, tool_name, e)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def process_response(coder, params):
|
|
105
|
+
"""
|
|
106
|
+
Process the ReplaceAll tool response.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
coder: The Coder instance
|
|
110
|
+
params: Dictionary of parameters
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
str: Result message
|
|
114
|
+
"""
|
|
115
|
+
file_path = params.get("file_path")
|
|
116
|
+
find_text = params.get("find_text")
|
|
117
|
+
replace_text = params.get("replace_text")
|
|
118
|
+
change_id = params.get("change_id")
|
|
119
|
+
dry_run = params.get("dry_run", False)
|
|
120
|
+
|
|
121
|
+
if file_path is not None and find_text is not None and replace_text is not None:
|
|
122
|
+
return _execute_replace_all(coder, file_path, find_text, replace_text, change_id, dry_run)
|
|
123
|
+
else:
|
|
124
|
+
return (
|
|
125
|
+
"Error: Missing required parameters for ReplaceAll (file_path, find_text, replace_text)"
|
|
126
|
+
)
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import traceback
|
|
3
|
+
|
|
4
|
+
schema = {
|
|
5
|
+
"type": "function",
|
|
6
|
+
"function": {
|
|
7
|
+
"name": "ReplaceLine",
|
|
8
|
+
"description": "Replace a single line in a file.",
|
|
9
|
+
"parameters": {
|
|
10
|
+
"type": "object",
|
|
11
|
+
"properties": {
|
|
12
|
+
"file_path": {"type": "string"},
|
|
13
|
+
"line_number": {"type": "integer"},
|
|
14
|
+
"new_content": {"type": "string"},
|
|
15
|
+
"change_id": {"type": "string"},
|
|
16
|
+
"dry_run": {"type": "boolean", "default": False},
|
|
17
|
+
},
|
|
18
|
+
"required": ["file_path", "line_number", "new_content"],
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
# Normalized tool name for lookup
|
|
24
|
+
NORM_NAME = "replaceline"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _execute_replace_line(
|
|
28
|
+
coder, file_path, line_number, new_content, change_id=None, dry_run=False
|
|
29
|
+
):
|
|
30
|
+
"""
|
|
31
|
+
Replace a specific line identified by line number.
|
|
32
|
+
Useful for fixing errors identified by error messages or linters.
|
|
33
|
+
|
|
34
|
+
Parameters:
|
|
35
|
+
- coder: The Coder instance
|
|
36
|
+
- file_path: Path to the file to modify
|
|
37
|
+
- line_number: The line number to replace (1-based)
|
|
38
|
+
- new_content: New content for the line
|
|
39
|
+
- change_id: Optional ID for tracking the change
|
|
40
|
+
- dry_run: If True, simulate the change without modifying the file
|
|
41
|
+
|
|
42
|
+
Returns a result message.
|
|
43
|
+
"""
|
|
44
|
+
try:
|
|
45
|
+
# Get absolute file path
|
|
46
|
+
abs_path = coder.abs_root_path(file_path)
|
|
47
|
+
rel_path = coder.get_rel_fname(abs_path)
|
|
48
|
+
|
|
49
|
+
# Check if file exists
|
|
50
|
+
if not os.path.isfile(abs_path):
|
|
51
|
+
coder.io.tool_error(f"File '{file_path}' not found")
|
|
52
|
+
return "Error: File not found"
|
|
53
|
+
|
|
54
|
+
# Check if file is in editable context
|
|
55
|
+
if abs_path not in coder.abs_fnames:
|
|
56
|
+
if abs_path in coder.abs_read_only_fnames:
|
|
57
|
+
coder.io.tool_error(f"File '{file_path}' is read-only. Use MakeEditable first.")
|
|
58
|
+
return "Error: File is read-only. Use MakeEditable first."
|
|
59
|
+
else:
|
|
60
|
+
coder.io.tool_error(f"File '{file_path}' not in context")
|
|
61
|
+
return "Error: File not in context"
|
|
62
|
+
|
|
63
|
+
# Reread file content immediately before modification
|
|
64
|
+
file_content = coder.io.read_text(abs_path)
|
|
65
|
+
if file_content is None:
|
|
66
|
+
coder.io.tool_error(f"Could not read file '{file_path}' before ReplaceLine operation.")
|
|
67
|
+
return f"Error: Could not read file '{file_path}'"
|
|
68
|
+
|
|
69
|
+
# Split into lines
|
|
70
|
+
lines = file_content.splitlines()
|
|
71
|
+
|
|
72
|
+
# Validate line number
|
|
73
|
+
if not isinstance(line_number, int):
|
|
74
|
+
try:
|
|
75
|
+
line_number = int(line_number)
|
|
76
|
+
except ValueError:
|
|
77
|
+
coder.io.tool_error(f"Line number must be an integer, got '{line_number}'")
|
|
78
|
+
coder.io.tool_error(
|
|
79
|
+
f"Invalid line_number value: '{line_number}'. Must be an integer."
|
|
80
|
+
)
|
|
81
|
+
return f"Error: Invalid line_number value '{line_number}'"
|
|
82
|
+
|
|
83
|
+
# Convert 1-based line number to 0-based index
|
|
84
|
+
idx = line_number - 1
|
|
85
|
+
|
|
86
|
+
if idx < 0 or idx >= len(lines):
|
|
87
|
+
coder.io.tool_error(
|
|
88
|
+
f"Line number {line_number} is out of range for file '{file_path}' (has"
|
|
89
|
+
f" {len(lines)} lines)."
|
|
90
|
+
)
|
|
91
|
+
return f"Error: Line number {line_number} out of range"
|
|
92
|
+
|
|
93
|
+
# Store original content for change tracking
|
|
94
|
+
original_content = file_content
|
|
95
|
+
original_line = lines[idx]
|
|
96
|
+
|
|
97
|
+
# Replace the line
|
|
98
|
+
lines[idx] = new_content
|
|
99
|
+
|
|
100
|
+
# Join lines back into a string
|
|
101
|
+
new_content_full = "\n".join(lines)
|
|
102
|
+
|
|
103
|
+
if original_content == new_content_full:
|
|
104
|
+
coder.io.tool_warning("No changes made: new line content is identical to original")
|
|
105
|
+
return "Warning: No changes made (new content identical to original)"
|
|
106
|
+
|
|
107
|
+
# Create a readable diff for the line replacement
|
|
108
|
+
diff = f"Line {line_number}:\n- {original_line}\n+ {new_content}"
|
|
109
|
+
|
|
110
|
+
# Handle dry run
|
|
111
|
+
if dry_run:
|
|
112
|
+
coder.io.tool_output(f"Dry run: Would replace line {line_number} in {file_path}")
|
|
113
|
+
return f"Dry run: Would replace line {line_number}. Diff:\n{diff}"
|
|
114
|
+
|
|
115
|
+
# --- Apply Change (Not dry run) ---
|
|
116
|
+
coder.io.write_text(abs_path, new_content_full)
|
|
117
|
+
|
|
118
|
+
# Track the change
|
|
119
|
+
try:
|
|
120
|
+
metadata = {
|
|
121
|
+
"line_number": line_number,
|
|
122
|
+
"original_line": original_line,
|
|
123
|
+
"new_line": new_content,
|
|
124
|
+
}
|
|
125
|
+
change_id = coder.change_tracker.track_change(
|
|
126
|
+
file_path=rel_path,
|
|
127
|
+
change_type="replaceline",
|
|
128
|
+
original_content=original_content,
|
|
129
|
+
new_content=new_content_full,
|
|
130
|
+
metadata=metadata,
|
|
131
|
+
change_id=change_id,
|
|
132
|
+
)
|
|
133
|
+
except Exception as track_e:
|
|
134
|
+
coder.io.tool_error(f"Error tracking change for ReplaceLine: {track_e}")
|
|
135
|
+
change_id = "TRACKING_FAILED"
|
|
136
|
+
|
|
137
|
+
coder.files_edited_by_tools.add(rel_path)
|
|
138
|
+
|
|
139
|
+
# Improve feedback
|
|
140
|
+
coder.io.tool_output(
|
|
141
|
+
f"✅ Replaced line {line_number} in {file_path} (change_id: {change_id})"
|
|
142
|
+
)
|
|
143
|
+
return f"Successfully replaced line {line_number} (change_id: {change_id}). Diff:\n{diff}"
|
|
144
|
+
|
|
145
|
+
except Exception as e:
|
|
146
|
+
coder.io.tool_error(f"Error in ReplaceLine: {str(e)}\n{traceback.format_exc()}")
|
|
147
|
+
return f"Error: {str(e)}"
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def process_response(coder, params):
|
|
151
|
+
"""
|
|
152
|
+
Process the ReplaceLine tool response.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
coder: The Coder instance
|
|
156
|
+
params: Dictionary of parameters
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
str: Result message
|
|
160
|
+
"""
|
|
161
|
+
file_path = params.get("file_path")
|
|
162
|
+
line_number = params.get("line_number")
|
|
163
|
+
new_content = params.get("new_content")
|
|
164
|
+
change_id = params.get("change_id")
|
|
165
|
+
dry_run = params.get("dry_run", False)
|
|
166
|
+
|
|
167
|
+
if file_path is not None and line_number is not None and new_content is not None:
|
|
168
|
+
return _execute_replace_line(coder, file_path, line_number, new_content, change_id, dry_run)
|
|
169
|
+
else:
|
|
170
|
+
return (
|
|
171
|
+
"Error: Missing required parameters for ReplaceLine (file_path,"
|
|
172
|
+
" line_number, new_content)"
|
|
173
|
+
)
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
from .tool_utils import (
|
|
4
|
+
ToolError,
|
|
5
|
+
apply_change,
|
|
6
|
+
format_tool_result,
|
|
7
|
+
generate_unified_diff_snippet,
|
|
8
|
+
handle_tool_error,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
schema = {
|
|
12
|
+
"type": "function",
|
|
13
|
+
"function": {
|
|
14
|
+
"name": "ReplaceLines",
|
|
15
|
+
"description": "Replace a range of lines in a file.",
|
|
16
|
+
"parameters": {
|
|
17
|
+
"type": "object",
|
|
18
|
+
"properties": {
|
|
19
|
+
"file_path": {"type": "string"},
|
|
20
|
+
"start_line": {"type": "integer"},
|
|
21
|
+
"end_line": {"type": "integer"},
|
|
22
|
+
"new_content": {"type": "string"},
|
|
23
|
+
"change_id": {"type": "string"},
|
|
24
|
+
"dry_run": {"type": "boolean", "default": False},
|
|
25
|
+
},
|
|
26
|
+
"required": ["file_path", "start_line", "end_line", "new_content"],
|
|
27
|
+
},
|
|
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.")
|
|
79
|
+
|
|
80
|
+
try:
|
|
81
|
+
end_line = int(end_line)
|
|
82
|
+
except ValueError:
|
|
83
|
+
raise ToolError(f"Invalid end_line value: '{end_line}'. Must be an integer.")
|
|
84
|
+
|
|
85
|
+
# Split into lines
|
|
86
|
+
lines = file_content.splitlines()
|
|
87
|
+
|
|
88
|
+
# Convert 1-based line numbers to 0-based indices
|
|
89
|
+
start_idx = start_line - 1
|
|
90
|
+
end_idx = end_line - 1
|
|
91
|
+
|
|
92
|
+
# Validate line numbers
|
|
93
|
+
if start_idx < 0 or start_idx >= len(lines):
|
|
94
|
+
raise ToolError(
|
|
95
|
+
f"Start line {start_line} is out of range for file '{file_path}' (has"
|
|
96
|
+
f" {len(lines)} lines)."
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
if end_idx < start_idx or end_idx >= len(lines):
|
|
100
|
+
raise ToolError(
|
|
101
|
+
f"End line {end_line} is out of range for file '{file_path}' (must be >= start line"
|
|
102
|
+
f" {start_line} and <= {len(lines)})."
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
# Store original content for change tracking
|
|
106
|
+
original_content = file_content
|
|
107
|
+
replaced_lines = lines[start_idx : end_idx + 1]
|
|
108
|
+
|
|
109
|
+
# Split the new content into lines
|
|
110
|
+
new_lines = new_content.splitlines()
|
|
111
|
+
|
|
112
|
+
# Perform the replacement
|
|
113
|
+
new_full_lines = lines[:start_idx] + new_lines + lines[end_idx + 1 :]
|
|
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}"
|
|
137
|
+
return format_tool_result(
|
|
138
|
+
coder,
|
|
139
|
+
tool_name,
|
|
140
|
+
"",
|
|
141
|
+
dry_run=True,
|
|
142
|
+
dry_run_message=dry_run_message,
|
|
143
|
+
diff_snippet=diff_snippet,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
# --- Apply Change (Not dry run) ---
|
|
147
|
+
metadata = {
|
|
148
|
+
"start_line": start_line,
|
|
149
|
+
"end_line": end_line,
|
|
150
|
+
"replaced_lines": replaced_lines,
|
|
151
|
+
"new_lines": new_lines,
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
final_change_id = apply_change(
|
|
155
|
+
coder,
|
|
156
|
+
abs_path,
|
|
157
|
+
rel_path,
|
|
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
|
+
)
|