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/utils.py ADDED
@@ -0,0 +1,456 @@
1
+ import os
2
+ import platform
3
+ import shutil
4
+ import subprocess
5
+ import sys
6
+ import tempfile
7
+ from pathlib import Path
8
+
9
+ import oslex
10
+
11
+ from aider.dump import dump # noqa: F401
12
+ from aider.waiting import Spinner
13
+
14
+ IMAGE_EXTENSIONS = {".png", ".jpg", ".jpeg", ".gif", ".bmp", ".tiff", ".webp", ".pdf"}
15
+
16
+
17
+ def run_fzf(input_data, multi=False):
18
+ """
19
+ Runs fzf as a subprocess, feeding it input_data.
20
+ Returns the selected items.
21
+ """
22
+ if not shutil.which("fzf"):
23
+ return [] # fzf not available
24
+
25
+ fzf_command = ["fzf"]
26
+ if multi:
27
+ fzf_command.append("--multi")
28
+
29
+ # Recommended flags for a good experience
30
+ fzf_command.extend(["--height", "80%", "--reverse"])
31
+
32
+ process = subprocess.Popen(
33
+ fzf_command,
34
+ stdin=subprocess.PIPE,
35
+ stdout=subprocess.PIPE,
36
+ text=True,
37
+ )
38
+ # fzf expects a newline-separated list of strings
39
+ stdout, _ = process.communicate("\n".join(input_data))
40
+
41
+ if process.returncode == 0:
42
+ # fzf returns selected items newline-separated
43
+ return stdout.strip().splitlines()
44
+ else:
45
+ # User cancelled (e.g., pressed Esc)
46
+ return []
47
+
48
+
49
+ class IgnorantTemporaryDirectory:
50
+ def __init__(self):
51
+ if sys.version_info >= (3, 10):
52
+ self.temp_dir = tempfile.TemporaryDirectory(ignore_cleanup_errors=True)
53
+ else:
54
+ self.temp_dir = tempfile.TemporaryDirectory()
55
+
56
+ def __enter__(self):
57
+ return self.temp_dir.__enter__()
58
+
59
+ def __exit__(self, exc_type, exc_val, exc_tb):
60
+ self.cleanup()
61
+
62
+ def cleanup(self):
63
+ try:
64
+ self.temp_dir.cleanup()
65
+ except (OSError, PermissionError, RecursionError):
66
+ pass # Ignore errors (Windows and potential recursion)
67
+
68
+ def __getattr__(self, item):
69
+ return getattr(self.temp_dir, item)
70
+
71
+
72
+ class ChdirTemporaryDirectory(IgnorantTemporaryDirectory):
73
+ def __init__(self):
74
+ try:
75
+ self.cwd = os.getcwd()
76
+ except FileNotFoundError:
77
+ self.cwd = None
78
+
79
+ super().__init__()
80
+
81
+ def __enter__(self):
82
+ res = super().__enter__()
83
+ os.chdir(Path(self.temp_dir.name).resolve())
84
+ return res
85
+
86
+ def __exit__(self, exc_type, exc_val, exc_tb):
87
+ if self.cwd:
88
+ try:
89
+ os.chdir(self.cwd)
90
+ except FileNotFoundError:
91
+ pass
92
+ super().__exit__(exc_type, exc_val, exc_tb)
93
+
94
+
95
+ class GitTemporaryDirectory(ChdirTemporaryDirectory):
96
+ def __enter__(self):
97
+ dname = super().__enter__()
98
+ self.repo = make_repo(dname)
99
+ return dname
100
+
101
+ def __exit__(self, exc_type, exc_val, exc_tb):
102
+ del self.repo
103
+ super().__exit__(exc_type, exc_val, exc_tb)
104
+
105
+
106
+ def make_repo(path=None):
107
+ import git
108
+
109
+ if not path:
110
+ path = "."
111
+ repo = git.Repo.init(path)
112
+ repo.config_writer().set_value("user", "name", "Test User").release()
113
+ repo.config_writer().set_value("user", "email", "testuser@example.com").release()
114
+
115
+ return repo
116
+
117
+
118
+ def is_image_file(file_name):
119
+ """
120
+ Check if the given file name has an image file extension.
121
+
122
+ :param file_name: The name of the file to check.
123
+ :return: True if the file is an image, False otherwise.
124
+ """
125
+ file_name = str(file_name) # Convert file_name to string
126
+ return any(file_name.endswith(ext) for ext in IMAGE_EXTENSIONS)
127
+
128
+
129
+ def safe_abs_path(res):
130
+ "Gives an abs path, which safely returns a full (not 8.3) windows path"
131
+ res = Path(res).resolve()
132
+ return str(res)
133
+
134
+
135
+ def format_content(role, content):
136
+ formatted_lines = []
137
+ for line in content.splitlines():
138
+ formatted_lines.append(f"{role} {line}")
139
+ return "\n".join(formatted_lines)
140
+
141
+
142
+ def format_messages(messages, title=None):
143
+ output = []
144
+ if title:
145
+ output.append(f"{title.upper()} {'*' * 50}")
146
+
147
+ for msg in messages:
148
+ output.append("-------")
149
+ role = msg["role"].upper()
150
+ content = msg.get("content")
151
+ if isinstance(content, list): # Handle list content (e.g., image messages)
152
+ for item in content:
153
+ if isinstance(item, dict):
154
+ for key, value in item.items():
155
+ if isinstance(value, dict) and "url" in value:
156
+ output.append(f"{role} {key.capitalize()} URL: {value['url']}")
157
+ else:
158
+ output.append(f"{role} {key}: {value}")
159
+ else:
160
+ output.append(f"{role} {item}")
161
+ elif isinstance(content, str): # Handle string content
162
+ # For large content, especially with many files, use a truncated display approach
163
+ if len(content) > 5000:
164
+ # Count the number of code blocks (approximation)
165
+ fence_count = content.count("```") // 2
166
+ if fence_count > 5:
167
+ # Show truncated content with file count for large files to improve performance
168
+ first_line = content.split("\n", 1)[0]
169
+ output.append(
170
+ f"{role} {first_line} [content with ~{fence_count} files truncated]"
171
+ )
172
+ else:
173
+ output.append(format_content(role, content))
174
+ else:
175
+ output.append(format_content(role, content))
176
+ function_call = msg.get("function_call")
177
+ if function_call:
178
+ output.append(f"{role} Function Call: {function_call}")
179
+
180
+ return "\n".join(output)
181
+
182
+
183
+ def show_messages(messages, title=None, functions=None):
184
+ formatted_output = format_messages(messages, title)
185
+ print(formatted_output)
186
+
187
+ if functions:
188
+ dump(functions)
189
+
190
+
191
+ def split_chat_history_markdown(text, include_tool=False):
192
+ messages = []
193
+ user = []
194
+ assistant = []
195
+ tool = []
196
+ lines = text.splitlines(keepends=True)
197
+
198
+ def append_msg(role, lines):
199
+ lines = "".join(lines)
200
+ if lines.strip():
201
+ messages.append(dict(role=role, content=lines))
202
+
203
+ for line in lines:
204
+ if line.startswith("# "):
205
+ continue
206
+ if line.startswith("> "):
207
+ append_msg("assistant", assistant)
208
+ assistant = []
209
+ append_msg("user", user)
210
+ user = []
211
+ tool.append(line[2:])
212
+ continue
213
+ # if line.startswith("#### /"):
214
+ # continue
215
+
216
+ if line.startswith("#### "):
217
+ append_msg("assistant", assistant)
218
+ assistant = []
219
+ append_msg("tool", tool)
220
+ tool = []
221
+
222
+ content = line[5:]
223
+ user.append(content)
224
+ continue
225
+
226
+ append_msg("user", user)
227
+ user = []
228
+ append_msg("tool", tool)
229
+ tool = []
230
+
231
+ assistant.append(line)
232
+
233
+ append_msg("assistant", assistant)
234
+ append_msg("user", user)
235
+
236
+ if not include_tool:
237
+ messages = [m for m in messages if m["role"] != "tool"]
238
+
239
+ return messages
240
+
241
+
242
+ def get_pip_install(args):
243
+ cmd = [
244
+ sys.executable,
245
+ "-m",
246
+ "pip",
247
+ "install",
248
+ "--upgrade",
249
+ "--upgrade-strategy",
250
+ "only-if-needed",
251
+ ]
252
+ cmd += args
253
+ return cmd
254
+
255
+
256
+ def run_install(cmd):
257
+ print()
258
+ print("Installing:", printable_shell_command(cmd))
259
+
260
+ # First ensure pip is available
261
+ ensurepip_cmd = [sys.executable, "-m", "ensurepip", "--upgrade"]
262
+ try:
263
+ subprocess.run(ensurepip_cmd, capture_output=True, check=False)
264
+ except Exception:
265
+ pass # Continue even if ensurepip fails
266
+
267
+ try:
268
+ output = []
269
+ process = subprocess.Popen(
270
+ cmd,
271
+ stdout=subprocess.PIPE,
272
+ stderr=subprocess.STDOUT,
273
+ text=True,
274
+ bufsize=1,
275
+ universal_newlines=True,
276
+ encoding=sys.stdout.encoding,
277
+ errors="replace",
278
+ )
279
+ spinner = Spinner("Installing...")
280
+
281
+ while True:
282
+ char = process.stdout.read(1)
283
+ if not char:
284
+ break
285
+
286
+ output.append(char)
287
+ spinner.step()
288
+
289
+ spinner.end()
290
+ return_code = process.wait()
291
+ output = "".join(output)
292
+
293
+ if return_code == 0:
294
+ print("Installation complete.")
295
+ print()
296
+ return True, output
297
+
298
+ except subprocess.CalledProcessError as e:
299
+ print(f"\nError running pip install: {e}")
300
+
301
+ print("\nInstallation failed.\n")
302
+
303
+ return False, output
304
+
305
+
306
+ def find_common_root(abs_fnames):
307
+ try:
308
+ if len(abs_fnames) == 1:
309
+ return safe_abs_path(os.path.dirname(list(abs_fnames)[0]))
310
+ elif abs_fnames:
311
+ return safe_abs_path(os.path.commonpath(list(abs_fnames)))
312
+ except OSError:
313
+ pass
314
+
315
+ try:
316
+ return safe_abs_path(os.getcwd())
317
+ except FileNotFoundError:
318
+ # Fallback if cwd is deleted
319
+ return "."
320
+
321
+
322
+ def format_tokens(count):
323
+ if count < 1000:
324
+ return f"{count}"
325
+ elif count < 10000:
326
+ return f"{count / 1000:.1f}k"
327
+ else:
328
+ return f"{round(count / 1000)}k"
329
+
330
+
331
+ def touch_file(fname):
332
+ fname = Path(fname)
333
+ try:
334
+ fname.parent.mkdir(parents=True, exist_ok=True)
335
+ fname.touch()
336
+ return True
337
+ except OSError:
338
+ return False
339
+
340
+
341
+ async def check_pip_install_extra(io, module, prompt, pip_install_cmd, self_update=False):
342
+ if module:
343
+ try:
344
+ __import__(module)
345
+ return True
346
+ except (ImportError, ModuleNotFoundError, RuntimeError):
347
+ pass
348
+
349
+ cmd = get_pip_install(pip_install_cmd)
350
+
351
+ if prompt:
352
+ io.tool_warning(prompt)
353
+
354
+ if self_update and platform.system() == "Windows":
355
+ io.tool_output("Run this command to update:")
356
+ print()
357
+ print(printable_shell_command(cmd)) # plain print so it doesn't line-wrap
358
+ return
359
+
360
+ if not await io.confirm_ask(
361
+ "Run pip install?", default="y", subject=printable_shell_command(cmd)
362
+ ):
363
+ return
364
+
365
+ success, output = run_install(cmd)
366
+ if success:
367
+ if not module:
368
+ return True
369
+ try:
370
+ __import__(module)
371
+ return True
372
+ except (ImportError, ModuleNotFoundError, RuntimeError) as err:
373
+ io.tool_error(str(err))
374
+ pass
375
+
376
+ io.tool_error(output)
377
+
378
+ print()
379
+ print("Install failed, try running this command manually:")
380
+ print(printable_shell_command(cmd))
381
+
382
+
383
+ def printable_shell_command(cmd_list):
384
+ """
385
+ Convert a list of command arguments to a properly shell-escaped string.
386
+
387
+ Args:
388
+ cmd_list (list): List of command arguments.
389
+
390
+ Returns:
391
+ str: Shell-escaped command string.
392
+ """
393
+ return oslex.join(cmd_list)
394
+
395
+
396
+ def split_concatenated_json(s: str) -> list[str]:
397
+ """
398
+ Splits a string containing one or more concatenated JSON objects.
399
+ """
400
+ res = []
401
+ i = 0
402
+ s_len = len(s)
403
+ while i < s_len:
404
+ # skip leading whitespace
405
+ while i < s_len and s[i].isspace():
406
+ i += 1
407
+ if i >= s_len:
408
+ break
409
+
410
+ start_char = s[i]
411
+ if start_char == "{":
412
+ end_char = "}"
413
+ elif start_char == "[":
414
+ end_char = "]"
415
+ else:
416
+ # Doesn't start with a JSON object/array, so we can't parse it as a stream.
417
+ # Return the rest of the string as a single chunk.
418
+ res.append(s[i:])
419
+ break
420
+
421
+ start_index = i
422
+ stack_depth = 0
423
+ in_string = False
424
+ escape = False
425
+
426
+ for j in range(start_index, s_len):
427
+ char = s[j]
428
+
429
+ if escape:
430
+ escape = False
431
+ continue
432
+
433
+ if char == "\\":
434
+ escape = True
435
+ continue
436
+
437
+ if char == '"':
438
+ in_string = not in_string
439
+
440
+ if in_string:
441
+ continue
442
+
443
+ if char == start_char:
444
+ stack_depth += 1
445
+ elif char == end_char:
446
+ stack_depth -= 1
447
+ if stack_depth == 0:
448
+ res.append(s[start_index : j + 1])
449
+ i = j + 1
450
+ break
451
+ else:
452
+ # Unclosed object, add the remainder as the last chunk
453
+ res.append(s[start_index:])
454
+ break
455
+
456
+ return res
aider/versioncheck.py ADDED
@@ -0,0 +1,113 @@
1
+ import os
2
+ import sys
3
+ import time
4
+ from pathlib import Path
5
+
6
+ import packaging.version
7
+
8
+ import aider
9
+ from aider import utils
10
+ from aider.dump import dump # noqa: F401
11
+
12
+ VERSION_CHECK_FNAME = Path.home() / ".aider" / "caches" / "versioncheck"
13
+
14
+
15
+ async def install_from_main_branch(io):
16
+ """
17
+ Install the latest version of aider from the main branch of the GitHub repository.
18
+ """
19
+
20
+ return await utils.check_pip_install_extra(
21
+ io,
22
+ None,
23
+ "Install the development version of aider from the main branch?",
24
+ ["git+https://github.com/dwash96/aider-ce.git"],
25
+ self_update=True,
26
+ )
27
+
28
+
29
+ async def install_upgrade(io, latest_version=None):
30
+ """
31
+ Install the latest version of aider from PyPI.
32
+ """
33
+
34
+ if latest_version:
35
+ new_ver_text = f"Newer aider-ce version v{latest_version} is available."
36
+ else:
37
+ new_ver_text = "Install latest version of aider?"
38
+
39
+ docker_image = os.environ.get("AIDER_DOCKER_IMAGE")
40
+ if docker_image:
41
+ text = f"""
42
+ {new_ver_text} To upgrade, run:
43
+
44
+ docker pull {docker_image}
45
+ """
46
+ io.tool_warning(text)
47
+ return True
48
+
49
+ success = await utils.check_pip_install_extra(
50
+ io,
51
+ None,
52
+ new_ver_text,
53
+ ["aider-ce"],
54
+ self_update=True,
55
+ )
56
+
57
+ if success:
58
+ io.tool_output("Re-run aider to use new version.")
59
+ sys.exit()
60
+
61
+ return
62
+
63
+
64
+ async def check_version(io, just_check=False, verbose=False):
65
+ if not just_check and VERSION_CHECK_FNAME.exists():
66
+ day = 60 * 60 * 24
67
+ since = time.time() - os.path.getmtime(VERSION_CHECK_FNAME)
68
+ if 0 < since < day:
69
+ if verbose:
70
+ hours = since / 60 / 60
71
+ io.tool_output(f"Too soon to check version: {hours:.1f} hours")
72
+ return
73
+
74
+ # To keep startup fast, avoid importing this unless needed
75
+ import requests
76
+
77
+ try:
78
+ response = requests.get("https://pypi.org/pypi/aider-ce/json")
79
+ data = response.json()
80
+ latest_version = data["info"]["version"]
81
+ current_version = aider.__version__
82
+
83
+ if just_check or verbose:
84
+ io.tool_output(f"Current version: {current_version}")
85
+ io.tool_output(f"Latest version: {latest_version}")
86
+
87
+ is_update_available = packaging.version.parse(latest_version) > packaging.version.parse(
88
+ current_version
89
+ )
90
+ except Exception as err:
91
+ io.tool_error(f"Error checking pypi for new version: {err}")
92
+ return False
93
+ finally:
94
+ VERSION_CHECK_FNAME.parent.mkdir(parents=True, exist_ok=True)
95
+ VERSION_CHECK_FNAME.touch()
96
+
97
+ ###
98
+ # is_update_available = True
99
+
100
+ if just_check or verbose:
101
+ if is_update_available:
102
+ io.tool_output("Update available")
103
+ else:
104
+ io.tool_output("No update available")
105
+
106
+ if just_check:
107
+ return is_update_available
108
+
109
+ if not is_update_available:
110
+ return False
111
+
112
+ await install_upgrade(io, latest_version)
113
+ return True