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.
Files changed (279) hide show
  1. aider/__init__.py +20 -0
  2. aider/__main__.py +4 -0
  3. aider/_version.py +34 -0
  4. aider/analytics.py +258 -0
  5. aider/args.py +1056 -0
  6. aider/args_formatter.py +228 -0
  7. aider/change_tracker.py +133 -0
  8. aider/coders/__init__.py +36 -0
  9. aider/coders/agent_coder.py +2166 -0
  10. aider/coders/agent_prompts.py +104 -0
  11. aider/coders/architect_coder.py +48 -0
  12. aider/coders/architect_prompts.py +40 -0
  13. aider/coders/ask_coder.py +9 -0
  14. aider/coders/ask_prompts.py +35 -0
  15. aider/coders/base_coder.py +3613 -0
  16. aider/coders/base_prompts.py +87 -0
  17. aider/coders/chat_chunks.py +64 -0
  18. aider/coders/context_coder.py +53 -0
  19. aider/coders/context_prompts.py +75 -0
  20. aider/coders/editblock_coder.py +657 -0
  21. aider/coders/editblock_fenced_coder.py +10 -0
  22. aider/coders/editblock_fenced_prompts.py +143 -0
  23. aider/coders/editblock_func_coder.py +141 -0
  24. aider/coders/editblock_func_prompts.py +27 -0
  25. aider/coders/editblock_prompts.py +175 -0
  26. aider/coders/editor_diff_fenced_coder.py +9 -0
  27. aider/coders/editor_diff_fenced_prompts.py +11 -0
  28. aider/coders/editor_editblock_coder.py +9 -0
  29. aider/coders/editor_editblock_prompts.py +21 -0
  30. aider/coders/editor_whole_coder.py +9 -0
  31. aider/coders/editor_whole_prompts.py +12 -0
  32. aider/coders/help_coder.py +16 -0
  33. aider/coders/help_prompts.py +46 -0
  34. aider/coders/patch_coder.py +706 -0
  35. aider/coders/patch_prompts.py +159 -0
  36. aider/coders/search_replace.py +757 -0
  37. aider/coders/shell.py +37 -0
  38. aider/coders/single_wholefile_func_coder.py +102 -0
  39. aider/coders/single_wholefile_func_prompts.py +27 -0
  40. aider/coders/udiff_coder.py +429 -0
  41. aider/coders/udiff_prompts.py +115 -0
  42. aider/coders/udiff_simple.py +14 -0
  43. aider/coders/udiff_simple_prompts.py +25 -0
  44. aider/coders/wholefile_coder.py +144 -0
  45. aider/coders/wholefile_func_coder.py +134 -0
  46. aider/coders/wholefile_func_prompts.py +27 -0
  47. aider/coders/wholefile_prompts.py +65 -0
  48. aider/commands.py +2173 -0
  49. aider/copypaste.py +72 -0
  50. aider/deprecated.py +126 -0
  51. aider/diffs.py +128 -0
  52. aider/dump.py +29 -0
  53. aider/editor.py +147 -0
  54. aider/exceptions.py +115 -0
  55. aider/format_settings.py +26 -0
  56. aider/gui.py +545 -0
  57. aider/help.py +163 -0
  58. aider/help_pats.py +19 -0
  59. aider/helpers/__init__.py +9 -0
  60. aider/helpers/similarity.py +98 -0
  61. aider/history.py +180 -0
  62. aider/io.py +1608 -0
  63. aider/linter.py +304 -0
  64. aider/llm.py +55 -0
  65. aider/main.py +1415 -0
  66. aider/mcp/__init__.py +174 -0
  67. aider/mcp/server.py +149 -0
  68. aider/mdstream.py +243 -0
  69. aider/models.py +1313 -0
  70. aider/onboarding.py +429 -0
  71. aider/openrouter.py +129 -0
  72. aider/prompts.py +56 -0
  73. aider/queries/tree-sitter-language-pack/README.md +7 -0
  74. aider/queries/tree-sitter-language-pack/arduino-tags.scm +5 -0
  75. aider/queries/tree-sitter-language-pack/c-tags.scm +9 -0
  76. aider/queries/tree-sitter-language-pack/chatito-tags.scm +16 -0
  77. aider/queries/tree-sitter-language-pack/clojure-tags.scm +7 -0
  78. aider/queries/tree-sitter-language-pack/commonlisp-tags.scm +122 -0
  79. aider/queries/tree-sitter-language-pack/cpp-tags.scm +15 -0
  80. aider/queries/tree-sitter-language-pack/csharp-tags.scm +26 -0
  81. aider/queries/tree-sitter-language-pack/d-tags.scm +26 -0
  82. aider/queries/tree-sitter-language-pack/dart-tags.scm +92 -0
  83. aider/queries/tree-sitter-language-pack/elisp-tags.scm +5 -0
  84. aider/queries/tree-sitter-language-pack/elixir-tags.scm +54 -0
  85. aider/queries/tree-sitter-language-pack/elm-tags.scm +19 -0
  86. aider/queries/tree-sitter-language-pack/gleam-tags.scm +41 -0
  87. aider/queries/tree-sitter-language-pack/go-tags.scm +42 -0
  88. aider/queries/tree-sitter-language-pack/java-tags.scm +20 -0
  89. aider/queries/tree-sitter-language-pack/javascript-tags.scm +88 -0
  90. aider/queries/tree-sitter-language-pack/lua-tags.scm +34 -0
  91. aider/queries/tree-sitter-language-pack/matlab-tags.scm +10 -0
  92. aider/queries/tree-sitter-language-pack/ocaml-tags.scm +115 -0
  93. aider/queries/tree-sitter-language-pack/ocaml_interface-tags.scm +98 -0
  94. aider/queries/tree-sitter-language-pack/pony-tags.scm +39 -0
  95. aider/queries/tree-sitter-language-pack/properties-tags.scm +5 -0
  96. aider/queries/tree-sitter-language-pack/python-tags.scm +14 -0
  97. aider/queries/tree-sitter-language-pack/r-tags.scm +21 -0
  98. aider/queries/tree-sitter-language-pack/racket-tags.scm +12 -0
  99. aider/queries/tree-sitter-language-pack/ruby-tags.scm +64 -0
  100. aider/queries/tree-sitter-language-pack/rust-tags.scm +60 -0
  101. aider/queries/tree-sitter-language-pack/solidity-tags.scm +43 -0
  102. aider/queries/tree-sitter-language-pack/swift-tags.scm +51 -0
  103. aider/queries/tree-sitter-language-pack/udev-tags.scm +20 -0
  104. aider/queries/tree-sitter-languages/README.md +24 -0
  105. aider/queries/tree-sitter-languages/c-tags.scm +9 -0
  106. aider/queries/tree-sitter-languages/c_sharp-tags.scm +46 -0
  107. aider/queries/tree-sitter-languages/cpp-tags.scm +15 -0
  108. aider/queries/tree-sitter-languages/dart-tags.scm +91 -0
  109. aider/queries/tree-sitter-languages/elisp-tags.scm +8 -0
  110. aider/queries/tree-sitter-languages/elixir-tags.scm +54 -0
  111. aider/queries/tree-sitter-languages/elm-tags.scm +19 -0
  112. aider/queries/tree-sitter-languages/fortran-tags.scm +15 -0
  113. aider/queries/tree-sitter-languages/go-tags.scm +30 -0
  114. aider/queries/tree-sitter-languages/haskell-tags.scm +3 -0
  115. aider/queries/tree-sitter-languages/hcl-tags.scm +77 -0
  116. aider/queries/tree-sitter-languages/java-tags.scm +20 -0
  117. aider/queries/tree-sitter-languages/javascript-tags.scm +88 -0
  118. aider/queries/tree-sitter-languages/julia-tags.scm +60 -0
  119. aider/queries/tree-sitter-languages/kotlin-tags.scm +27 -0
  120. aider/queries/tree-sitter-languages/matlab-tags.scm +10 -0
  121. aider/queries/tree-sitter-languages/ocaml-tags.scm +115 -0
  122. aider/queries/tree-sitter-languages/ocaml_interface-tags.scm +98 -0
  123. aider/queries/tree-sitter-languages/php-tags.scm +26 -0
  124. aider/queries/tree-sitter-languages/python-tags.scm +12 -0
  125. aider/queries/tree-sitter-languages/ql-tags.scm +26 -0
  126. aider/queries/tree-sitter-languages/ruby-tags.scm +64 -0
  127. aider/queries/tree-sitter-languages/rust-tags.scm +60 -0
  128. aider/queries/tree-sitter-languages/scala-tags.scm +65 -0
  129. aider/queries/tree-sitter-languages/typescript-tags.scm +41 -0
  130. aider/queries/tree-sitter-languages/zig-tags.scm +3 -0
  131. aider/reasoning_tags.py +82 -0
  132. aider/repo.py +621 -0
  133. aider/repomap.py +1174 -0
  134. aider/report.py +260 -0
  135. aider/resources/__init__.py +3 -0
  136. aider/resources/model-metadata.json +776 -0
  137. aider/resources/model-settings.yml +2068 -0
  138. aider/run_cmd.py +133 -0
  139. aider/scrape.py +293 -0
  140. aider/sendchat.py +242 -0
  141. aider/sessions.py +256 -0
  142. aider/special.py +203 -0
  143. aider/tools/__init__.py +72 -0
  144. aider/tools/command.py +105 -0
  145. aider/tools/command_interactive.py +122 -0
  146. aider/tools/delete_block.py +182 -0
  147. aider/tools/delete_line.py +155 -0
  148. aider/tools/delete_lines.py +184 -0
  149. aider/tools/extract_lines.py +341 -0
  150. aider/tools/finished.py +48 -0
  151. aider/tools/git_branch.py +129 -0
  152. aider/tools/git_diff.py +60 -0
  153. aider/tools/git_log.py +57 -0
  154. aider/tools/git_remote.py +53 -0
  155. aider/tools/git_show.py +51 -0
  156. aider/tools/git_status.py +46 -0
  157. aider/tools/grep.py +256 -0
  158. aider/tools/indent_lines.py +221 -0
  159. aider/tools/insert_block.py +288 -0
  160. aider/tools/list_changes.py +86 -0
  161. aider/tools/ls.py +93 -0
  162. aider/tools/make_editable.py +85 -0
  163. aider/tools/make_readonly.py +69 -0
  164. aider/tools/remove.py +91 -0
  165. aider/tools/replace_all.py +126 -0
  166. aider/tools/replace_line.py +173 -0
  167. aider/tools/replace_lines.py +217 -0
  168. aider/tools/replace_text.py +187 -0
  169. aider/tools/show_numbered_context.py +147 -0
  170. aider/tools/tool_utils.py +313 -0
  171. aider/tools/undo_change.py +95 -0
  172. aider/tools/update_todo_list.py +156 -0
  173. aider/tools/view.py +57 -0
  174. aider/tools/view_files_matching.py +141 -0
  175. aider/tools/view_files_with_symbol.py +129 -0
  176. aider/urls.py +17 -0
  177. aider/utils.py +456 -0
  178. aider/versioncheck.py +113 -0
  179. aider/voice.py +205 -0
  180. aider/waiting.py +38 -0
  181. aider/watch.py +318 -0
  182. aider/watch_prompts.py +12 -0
  183. aider/website/Gemfile +8 -0
  184. aider/website/_includes/blame.md +162 -0
  185. aider/website/_includes/get-started.md +22 -0
  186. aider/website/_includes/help-tip.md +5 -0
  187. aider/website/_includes/help.md +24 -0
  188. aider/website/_includes/install.md +5 -0
  189. aider/website/_includes/keys.md +4 -0
  190. aider/website/_includes/model-warnings.md +67 -0
  191. aider/website/_includes/multi-line.md +22 -0
  192. aider/website/_includes/python-m-aider.md +5 -0
  193. aider/website/_includes/recording.css +228 -0
  194. aider/website/_includes/recording.md +34 -0
  195. aider/website/_includes/replit-pipx.md +9 -0
  196. aider/website/_includes/works-best.md +1 -0
  197. aider/website/_sass/custom/custom.scss +103 -0
  198. aider/website/docs/config/adv-model-settings.md +2261 -0
  199. aider/website/docs/config/agent-mode.md +194 -0
  200. aider/website/docs/config/aider_conf.md +548 -0
  201. aider/website/docs/config/api-keys.md +90 -0
  202. aider/website/docs/config/dotenv.md +493 -0
  203. aider/website/docs/config/editor.md +127 -0
  204. aider/website/docs/config/mcp.md +95 -0
  205. aider/website/docs/config/model-aliases.md +104 -0
  206. aider/website/docs/config/options.md +890 -0
  207. aider/website/docs/config/reasoning.md +210 -0
  208. aider/website/docs/config.md +44 -0
  209. aider/website/docs/faq.md +384 -0
  210. aider/website/docs/git.md +76 -0
  211. aider/website/docs/index.md +47 -0
  212. aider/website/docs/install/codespaces.md +39 -0
  213. aider/website/docs/install/docker.md +57 -0
  214. aider/website/docs/install/optional.md +100 -0
  215. aider/website/docs/install/replit.md +8 -0
  216. aider/website/docs/install.md +115 -0
  217. aider/website/docs/languages.md +264 -0
  218. aider/website/docs/legal/contributor-agreement.md +111 -0
  219. aider/website/docs/legal/privacy.md +104 -0
  220. aider/website/docs/llms/anthropic.md +77 -0
  221. aider/website/docs/llms/azure.md +48 -0
  222. aider/website/docs/llms/bedrock.md +132 -0
  223. aider/website/docs/llms/cohere.md +34 -0
  224. aider/website/docs/llms/deepseek.md +32 -0
  225. aider/website/docs/llms/gemini.md +49 -0
  226. aider/website/docs/llms/github.md +111 -0
  227. aider/website/docs/llms/groq.md +36 -0
  228. aider/website/docs/llms/lm-studio.md +39 -0
  229. aider/website/docs/llms/ollama.md +75 -0
  230. aider/website/docs/llms/openai-compat.md +39 -0
  231. aider/website/docs/llms/openai.md +58 -0
  232. aider/website/docs/llms/openrouter.md +78 -0
  233. aider/website/docs/llms/other.md +117 -0
  234. aider/website/docs/llms/vertex.md +50 -0
  235. aider/website/docs/llms/warnings.md +10 -0
  236. aider/website/docs/llms/xai.md +53 -0
  237. aider/website/docs/llms.md +54 -0
  238. aider/website/docs/more/analytics.md +127 -0
  239. aider/website/docs/more/edit-formats.md +116 -0
  240. aider/website/docs/more/infinite-output.md +165 -0
  241. aider/website/docs/more-info.md +8 -0
  242. aider/website/docs/recordings/auto-accept-architect.md +31 -0
  243. aider/website/docs/recordings/dont-drop-original-read-files.md +35 -0
  244. aider/website/docs/recordings/index.md +21 -0
  245. aider/website/docs/recordings/model-accepts-settings.md +69 -0
  246. aider/website/docs/recordings/tree-sitter-language-pack.md +80 -0
  247. aider/website/docs/repomap.md +112 -0
  248. aider/website/docs/scripting.md +100 -0
  249. aider/website/docs/sessions.md +203 -0
  250. aider/website/docs/troubleshooting/aider-not-found.md +24 -0
  251. aider/website/docs/troubleshooting/edit-errors.md +76 -0
  252. aider/website/docs/troubleshooting/imports.md +62 -0
  253. aider/website/docs/troubleshooting/models-and-keys.md +54 -0
  254. aider/website/docs/troubleshooting/support.md +79 -0
  255. aider/website/docs/troubleshooting/token-limits.md +96 -0
  256. aider/website/docs/troubleshooting/warnings.md +12 -0
  257. aider/website/docs/troubleshooting.md +11 -0
  258. aider/website/docs/usage/browser.md +57 -0
  259. aider/website/docs/usage/caching.md +49 -0
  260. aider/website/docs/usage/commands.md +133 -0
  261. aider/website/docs/usage/conventions.md +119 -0
  262. aider/website/docs/usage/copypaste.md +121 -0
  263. aider/website/docs/usage/images-urls.md +48 -0
  264. aider/website/docs/usage/lint-test.md +118 -0
  265. aider/website/docs/usage/modes.md +211 -0
  266. aider/website/docs/usage/not-code.md +179 -0
  267. aider/website/docs/usage/notifications.md +87 -0
  268. aider/website/docs/usage/tips.md +79 -0
  269. aider/website/docs/usage/tutorials.md +30 -0
  270. aider/website/docs/usage/voice.md +121 -0
  271. aider/website/docs/usage/watch.md +294 -0
  272. aider/website/docs/usage.md +102 -0
  273. aider/website/share/index.md +101 -0
  274. aider_ce-0.88.20.dist-info/METADATA +187 -0
  275. aider_ce-0.88.20.dist-info/RECORD +279 -0
  276. aider_ce-0.88.20.dist-info/WHEEL +5 -0
  277. aider_ce-0.88.20.dist-info/entry_points.txt +2 -0
  278. aider_ce-0.88.20.dist-info/licenses/LICENSE.txt +202 -0
  279. 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
+ )