aider-ce 0.88.3__py3-none-any.whl → 0.88.6.dev7__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.
Potentially problematic release.
This version of aider-ce might be problematic. Click here for more details.
- aider/__init__.py +1 -1
- aider/_version.py +2 -2
- aider/args.py +14 -2
- aider/coders/base_coder.py +162 -50
- aider/commands.py +248 -47
- aider/exceptions.py +3 -0
- aider/io.py +107 -94
- aider/main.py +4 -0
- aider/queries/tree-sitter-languages/haskell-tags.scm +3 -0
- aider/queries/tree-sitter-languages/zig-tags.scm +3 -0
- aider/repo.py +1 -1
- aider/urls.py +1 -1
- aider/versioncheck.py +1 -1
- aider/website/docs/sessions.md +182 -0
- {aider_ce-0.88.3.dist-info → aider_ce-0.88.6.dev7.dist-info}/METADATA +3 -1
- {aider_ce-0.88.3.dist-info → aider_ce-0.88.6.dev7.dist-info}/RECORD +20 -17
- {aider_ce-0.88.3.dist-info → aider_ce-0.88.6.dev7.dist-info}/WHEEL +0 -0
- {aider_ce-0.88.3.dist-info → aider_ce-0.88.6.dev7.dist-info}/entry_points.txt +0 -0
- {aider_ce-0.88.3.dist-info → aider_ce-0.88.6.dev7.dist-info}/licenses/LICENSE.txt +0 -0
- {aider_ce-0.88.3.dist-info → aider_ce-0.88.6.dev7.dist-info}/top_level.txt +0 -0
aider/commands.py
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import glob
|
|
3
|
+
import json
|
|
3
4
|
import os
|
|
4
5
|
import re
|
|
5
6
|
import subprocess
|
|
6
7
|
import sys
|
|
7
8
|
import tempfile
|
|
9
|
+
import time
|
|
8
10
|
from collections import OrderedDict
|
|
11
|
+
from datetime import datetime
|
|
9
12
|
from os.path import expanduser
|
|
10
13
|
from pathlib import Path
|
|
11
14
|
|
|
@@ -84,6 +87,7 @@ class Commands:
|
|
|
84
87
|
|
|
85
88
|
# Store the original read-only filenames provided via args.read
|
|
86
89
|
self.original_read_only_fnames = set(original_read_only_fnames or [])
|
|
90
|
+
self.cmd_running = False
|
|
87
91
|
|
|
88
92
|
def cmd_model(self, args):
|
|
89
93
|
"Switch the Main Model to a new LLM"
|
|
@@ -256,6 +260,9 @@ class Commands:
|
|
|
256
260
|
def is_command(self, inp):
|
|
257
261
|
return inp[0] in "/!"
|
|
258
262
|
|
|
263
|
+
def is_run_command(self, inp):
|
|
264
|
+
return inp and (inp[0] in "!" or inp[:5] == "/test" or inp[:4] == "/run")
|
|
265
|
+
|
|
259
266
|
def get_raw_completions(self, cmd):
|
|
260
267
|
assert cmd.startswith("/")
|
|
261
268
|
cmd = cmd[1:]
|
|
@@ -1151,51 +1158,61 @@ class Commands:
|
|
|
1151
1158
|
|
|
1152
1159
|
async def cmd_run(self, args, add_on_nonzero_exit=False):
|
|
1153
1160
|
"Run a shell command and optionally add the output to the chat (alias: !)"
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
+
try:
|
|
1162
|
+
self.cmd_running = True
|
|
1163
|
+
exit_status, combined_output = await asyncio.to_thread(
|
|
1164
|
+
run_cmd,
|
|
1165
|
+
args,
|
|
1166
|
+
verbose=self.verbose,
|
|
1167
|
+
error_print=self.io.tool_error,
|
|
1168
|
+
cwd=self.coder.root,
|
|
1169
|
+
)
|
|
1170
|
+
self.cmd_running = False
|
|
1161
1171
|
|
|
1162
|
-
|
|
1163
|
-
|
|
1172
|
+
# This print statement, for whatever reason,
|
|
1173
|
+
# allows the thread to properly yield control of the terminal
|
|
1174
|
+
# to the main program
|
|
1175
|
+
print("")
|
|
1164
1176
|
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
k_tokens = token_count / 1000
|
|
1177
|
+
if combined_output is None:
|
|
1178
|
+
return
|
|
1168
1179
|
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
add = await self.io.confirm_ask(
|
|
1173
|
-
f"Add {k_tokens:.1f}k tokens of command output to the chat?"
|
|
1174
|
-
)
|
|
1180
|
+
# Calculate token count of output
|
|
1181
|
+
token_count = self.coder.main_model.token_count(combined_output)
|
|
1182
|
+
k_tokens = token_count / 1000
|
|
1175
1183
|
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1184
|
+
if add_on_nonzero_exit:
|
|
1185
|
+
add = exit_status != 0
|
|
1186
|
+
else:
|
|
1187
|
+
add = await self.io.confirm_ask(
|
|
1188
|
+
f"Add {k_tokens:.1f}k tokens of command output to the chat?"
|
|
1189
|
+
)
|
|
1180
1190
|
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1191
|
+
if add:
|
|
1192
|
+
num_lines = len(combined_output.strip().splitlines())
|
|
1193
|
+
line_plural = "line" if num_lines == 1 else "lines"
|
|
1194
|
+
self.io.tool_output(f"Added {num_lines} {line_plural} of output to the chat.")
|
|
1185
1195
|
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1196
|
+
msg = prompts.run_output.format(
|
|
1197
|
+
command=args,
|
|
1198
|
+
output=combined_output,
|
|
1199
|
+
)
|
|
1190
1200
|
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
self.io.placeholder = "What's wrong? Fix"
|
|
1201
|
+
self.coder.cur_messages += [
|
|
1202
|
+
dict(role="user", content=msg),
|
|
1203
|
+
dict(role="assistant", content="Ok."),
|
|
1204
|
+
]
|
|
1196
1205
|
|
|
1197
|
-
|
|
1198
|
-
|
|
1206
|
+
if add_on_nonzero_exit and exit_status != 0:
|
|
1207
|
+
# Return the formatted output message for test failures
|
|
1208
|
+
return msg
|
|
1209
|
+
elif add and exit_status != 0:
|
|
1210
|
+
self.io.placeholder = "What's wrong? Fix"
|
|
1211
|
+
|
|
1212
|
+
# Return None if output wasn't added or command succeeded
|
|
1213
|
+
return None
|
|
1214
|
+
finally:
|
|
1215
|
+
self.cmd_running = False
|
|
1199
1216
|
|
|
1200
1217
|
def cmd_exit(self, args):
|
|
1201
1218
|
"Exit the application"
|
|
@@ -1476,19 +1493,12 @@ class Commands:
|
|
|
1476
1493
|
edit_format=edit_format,
|
|
1477
1494
|
summarize_from_coder=False,
|
|
1478
1495
|
num_cache_warming_pings=0,
|
|
1496
|
+
aider_commit_hashes=self.coder.aider_commit_hashes,
|
|
1479
1497
|
)
|
|
1480
1498
|
|
|
1481
1499
|
user_msg = args
|
|
1482
|
-
await coder.run(user_msg)
|
|
1483
|
-
|
|
1484
|
-
# Use the provided placeholder if any
|
|
1485
|
-
raise SwitchCoder(
|
|
1486
|
-
edit_format=self.coder.edit_format,
|
|
1487
|
-
summarize_from_coder=False,
|
|
1488
|
-
from_coder=coder,
|
|
1489
|
-
show_announcements=False,
|
|
1490
|
-
placeholder=placeholder,
|
|
1491
|
-
)
|
|
1500
|
+
await coder.run(user_msg, False)
|
|
1501
|
+
self.coder.aider_commit_hashes = coder.aider_commit_hashes
|
|
1492
1502
|
|
|
1493
1503
|
def get_help_md(self):
|
|
1494
1504
|
"Show help about all commands in markdown"
|
|
@@ -2021,6 +2031,197 @@ class Commands:
|
|
|
2021
2031
|
announcements = "\n".join(self.coder.get_announcements())
|
|
2022
2032
|
self.io.tool_output(announcements)
|
|
2023
2033
|
|
|
2034
|
+
def _get_session_directory(self):
|
|
2035
|
+
"""Get the session storage directory, creating it if needed"""
|
|
2036
|
+
session_dir = Path(self.coder.root) / ".aider" / "sessions"
|
|
2037
|
+
session_dir.mkdir(parents=True, exist_ok=True)
|
|
2038
|
+
return session_dir
|
|
2039
|
+
|
|
2040
|
+
def _get_session_file_path(self, session_name):
|
|
2041
|
+
"""Get the full path for a session file"""
|
|
2042
|
+
session_dir = self._get_session_directory()
|
|
2043
|
+
# Sanitize the session name to be filesystem-safe
|
|
2044
|
+
safe_name = re.sub(r"[^a-zA-Z0-9_.-]", "_", session_name)
|
|
2045
|
+
return session_dir / f"{safe_name}.json"
|
|
2046
|
+
|
|
2047
|
+
def _find_session_file(self, session_name):
|
|
2048
|
+
"""Find a session file by name, checking both name-based and full path"""
|
|
2049
|
+
# First check if it's a full path
|
|
2050
|
+
if Path(session_name).exists():
|
|
2051
|
+
return Path(session_name)
|
|
2052
|
+
|
|
2053
|
+
# Then check in the sessions directory
|
|
2054
|
+
session_file = self._get_session_file_path(session_name)
|
|
2055
|
+
if session_file.exists():
|
|
2056
|
+
return session_file
|
|
2057
|
+
|
|
2058
|
+
return None
|
|
2059
|
+
|
|
2060
|
+
def cmd_save_session(self, args):
|
|
2061
|
+
"""Save the current chat session to a named file in .aider/sessions/"""
|
|
2062
|
+
if not args.strip():
|
|
2063
|
+
self.io.tool_error("Please provide a session name.")
|
|
2064
|
+
return
|
|
2065
|
+
|
|
2066
|
+
session_name = args.strip()
|
|
2067
|
+
session_file = self._get_session_file_path(session_name)
|
|
2068
|
+
|
|
2069
|
+
# Collect session data
|
|
2070
|
+
session_data = {
|
|
2071
|
+
"version": "1.0",
|
|
2072
|
+
"timestamp": time.time(),
|
|
2073
|
+
"session_name": session_name,
|
|
2074
|
+
"model": self.coder.main_model.name,
|
|
2075
|
+
"edit_format": self.coder.edit_format,
|
|
2076
|
+
"chat_history": {
|
|
2077
|
+
"done_messages": self.coder.done_messages,
|
|
2078
|
+
"cur_messages": self.coder.cur_messages,
|
|
2079
|
+
},
|
|
2080
|
+
"files": {
|
|
2081
|
+
"editable": [self.coder.get_rel_fname(f) for f in self.coder.abs_fnames],
|
|
2082
|
+
"read_only": [self.coder.get_rel_fname(f) for f in self.coder.abs_read_only_fnames],
|
|
2083
|
+
"read_only_stubs": [
|
|
2084
|
+
self.coder.get_rel_fname(f) for f in self.coder.abs_read_only_stubs_fnames
|
|
2085
|
+
],
|
|
2086
|
+
},
|
|
2087
|
+
"settings": {
|
|
2088
|
+
"root": self.coder.root,
|
|
2089
|
+
"auto_commits": self.coder.auto_commits,
|
|
2090
|
+
"auto_lint": self.coder.auto_lint,
|
|
2091
|
+
"auto_test": self.coder.auto_test,
|
|
2092
|
+
},
|
|
2093
|
+
}
|
|
2094
|
+
|
|
2095
|
+
try:
|
|
2096
|
+
with open(session_file, "w", encoding="utf-8") as f:
|
|
2097
|
+
json.dump(session_data, f, indent=2, ensure_ascii=False)
|
|
2098
|
+
self.io.tool_output(f"Session saved to: {session_file}")
|
|
2099
|
+
except Exception as e:
|
|
2100
|
+
self.io.tool_error(f"Error saving session: {e}")
|
|
2101
|
+
|
|
2102
|
+
def cmd_list_sessions(self, args):
|
|
2103
|
+
"""List all saved sessions in .aider/sessions/"""
|
|
2104
|
+
session_dir = self._get_session_directory()
|
|
2105
|
+
session_files = list(session_dir.glob("*.json"))
|
|
2106
|
+
|
|
2107
|
+
if not session_files:
|
|
2108
|
+
self.io.tool_output("No saved sessions found.")
|
|
2109
|
+
return
|
|
2110
|
+
|
|
2111
|
+
self.io.tool_output("Saved sessions:")
|
|
2112
|
+
for session_file in sorted(session_files):
|
|
2113
|
+
try:
|
|
2114
|
+
with open(session_file, "r", encoding="utf-8") as f:
|
|
2115
|
+
session_data = json.load(f)
|
|
2116
|
+
session_name = session_data.get("session_name", session_file.stem)
|
|
2117
|
+
timestamp = session_data.get("timestamp", 0)
|
|
2118
|
+
model = session_data.get("model", "unknown")
|
|
2119
|
+
edit_format = session_data.get("edit_format", "unknown")
|
|
2120
|
+
|
|
2121
|
+
# Format timestamp
|
|
2122
|
+
if timestamp:
|
|
2123
|
+
date_str = datetime.fromtimestamp(timestamp).strftime("%Y-%m-%d %H:%M")
|
|
2124
|
+
else:
|
|
2125
|
+
date_str = "unknown date"
|
|
2126
|
+
|
|
2127
|
+
self.io.tool_output(
|
|
2128
|
+
f" {session_name} (model: {model}, format: {edit_format}, {date_str})"
|
|
2129
|
+
)
|
|
2130
|
+
except Exception as e:
|
|
2131
|
+
self.io.tool_output(f" {session_file.stem} [error reading: {e}]")
|
|
2132
|
+
|
|
2133
|
+
def cmd_load_session(self, args):
|
|
2134
|
+
"""Load a saved session by name or file path"""
|
|
2135
|
+
if not args.strip():
|
|
2136
|
+
self.io.tool_error("Please provide a session name or file path.")
|
|
2137
|
+
return
|
|
2138
|
+
|
|
2139
|
+
session_name = args.strip()
|
|
2140
|
+
session_file = self._find_session_file(session_name)
|
|
2141
|
+
|
|
2142
|
+
if not session_file:
|
|
2143
|
+
self.io.tool_error(f"Session not found: {session_name}")
|
|
2144
|
+
self.io.tool_output("Use /list-sessions to see available sessions.")
|
|
2145
|
+
return
|
|
2146
|
+
|
|
2147
|
+
try:
|
|
2148
|
+
with open(session_file, "r", encoding="utf-8") as f:
|
|
2149
|
+
session_data = json.load(f)
|
|
2150
|
+
except Exception as e:
|
|
2151
|
+
self.io.tool_error(f"Error loading session: {e}")
|
|
2152
|
+
return
|
|
2153
|
+
|
|
2154
|
+
# Verify session format
|
|
2155
|
+
if not isinstance(session_data, dict) or "version" not in session_data:
|
|
2156
|
+
self.io.tool_error("Invalid session format.")
|
|
2157
|
+
return
|
|
2158
|
+
|
|
2159
|
+
# Load session data
|
|
2160
|
+
try:
|
|
2161
|
+
# Clear current state
|
|
2162
|
+
self.coder.abs_fnames = set()
|
|
2163
|
+
self.coder.abs_read_only_fnames = set()
|
|
2164
|
+
self.coder.abs_read_only_stubs_fnames = set()
|
|
2165
|
+
self.coder.done_messages = []
|
|
2166
|
+
self.coder.cur_messages = []
|
|
2167
|
+
|
|
2168
|
+
# Load chat history
|
|
2169
|
+
chat_history = session_data.get("chat_history", {})
|
|
2170
|
+
self.coder.done_messages = chat_history.get("done_messages", [])
|
|
2171
|
+
self.coder.cur_messages = chat_history.get("cur_messages", [])
|
|
2172
|
+
|
|
2173
|
+
# Load files
|
|
2174
|
+
files = session_data.get("files", {})
|
|
2175
|
+
for rel_fname in files.get("editable", []):
|
|
2176
|
+
abs_fname = self.coder.abs_root_path(rel_fname)
|
|
2177
|
+
if os.path.exists(abs_fname):
|
|
2178
|
+
self.coder.abs_fnames.add(abs_fname)
|
|
2179
|
+
else:
|
|
2180
|
+
self.io.tool_warning(f"File not found, skipping: {rel_fname}")
|
|
2181
|
+
|
|
2182
|
+
for rel_fname in files.get("read_only", []):
|
|
2183
|
+
abs_fname = self.coder.abs_root_path(rel_fname)
|
|
2184
|
+
if os.path.exists(abs_fname):
|
|
2185
|
+
self.coder.abs_read_only_fnames.add(abs_fname)
|
|
2186
|
+
else:
|
|
2187
|
+
self.io.tool_warning(f"File not found, skipping: {rel_fname}")
|
|
2188
|
+
|
|
2189
|
+
for rel_fname in files.get("read_only_stubs", []):
|
|
2190
|
+
abs_fname = self.coder.abs_root_path(rel_fname)
|
|
2191
|
+
if os.path.exists(abs_fname):
|
|
2192
|
+
self.coder.abs_read_only_stubs_fnames.add(abs_fname)
|
|
2193
|
+
else:
|
|
2194
|
+
self.io.tool_warning(f"File not found, skipping: {rel_fname}")
|
|
2195
|
+
|
|
2196
|
+
# Load settings
|
|
2197
|
+
settings = session_data.get("settings", {})
|
|
2198
|
+
if "auto_commits" in settings:
|
|
2199
|
+
self.coder.auto_commits = settings["auto_commits"]
|
|
2200
|
+
if "auto_lint" in settings:
|
|
2201
|
+
self.coder.auto_lint = settings["auto_lint"]
|
|
2202
|
+
if "auto_test" in settings:
|
|
2203
|
+
self.coder.auto_test = settings["auto_test"]
|
|
2204
|
+
|
|
2205
|
+
self.io.tool_output(
|
|
2206
|
+
f"Session loaded: {session_data.get('session_name', session_file.stem)}"
|
|
2207
|
+
)
|
|
2208
|
+
self.io.tool_output(
|
|
2209
|
+
f"Model: {session_data.get('model', 'unknown')}, Edit format:"
|
|
2210
|
+
f" {session_data.get('edit_format', 'unknown')}"
|
|
2211
|
+
)
|
|
2212
|
+
|
|
2213
|
+
# Show summary
|
|
2214
|
+
num_messages = len(self.coder.done_messages) + len(self.coder.cur_messages)
|
|
2215
|
+
num_files = (
|
|
2216
|
+
len(self.coder.abs_fnames)
|
|
2217
|
+
+ len(self.coder.abs_read_only_fnames)
|
|
2218
|
+
+ len(self.coder.abs_read_only_stubs_fnames)
|
|
2219
|
+
)
|
|
2220
|
+
self.io.tool_output(f"Loaded {num_messages} messages and {num_files} files")
|
|
2221
|
+
|
|
2222
|
+
except Exception as e:
|
|
2223
|
+
self.io.tool_error(f"Error applying session data: {e}")
|
|
2224
|
+
|
|
2024
2225
|
def cmd_copy_context(self, args=None):
|
|
2025
2226
|
"""Copy the current chat context as markdown, suitable to paste into a web UI"""
|
|
2026
2227
|
|
aider/exceptions.py
CHANGED