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/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
- exit_status, combined_output = await asyncio.to_thread(
1155
- run_cmd,
1156
- args,
1157
- verbose=self.verbose,
1158
- error_print=self.io.tool_error,
1159
- cwd=self.coder.root,
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
- if combined_output is None:
1163
- return
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
- # Calculate token count of output
1166
- token_count = self.coder.main_model.token_count(combined_output)
1167
- k_tokens = token_count / 1000
1177
+ if combined_output is None:
1178
+ return
1168
1179
 
1169
- if add_on_nonzero_exit:
1170
- add = exit_status != 0
1171
- else:
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
- if add:
1177
- num_lines = len(combined_output.strip().splitlines())
1178
- line_plural = "line" if num_lines == 1 else "lines"
1179
- self.io.tool_output(f"Added {num_lines} {line_plural} of output to the chat.")
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
- msg = prompts.run_output.format(
1182
- command=args,
1183
- output=combined_output,
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
- self.coder.cur_messages += [
1187
- dict(role="user", content=msg),
1188
- dict(role="assistant", content="Ok."),
1189
- ]
1196
+ msg = prompts.run_output.format(
1197
+ command=args,
1198
+ output=combined_output,
1199
+ )
1190
1200
 
1191
- if add_on_nonzero_exit and exit_status != 0:
1192
- # Return the formatted output message for test failures
1193
- return msg
1194
- elif add and exit_status != 0:
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
- # Return None if output wasn't added or command succeeded
1198
- return None
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
@@ -71,6 +71,9 @@ class LiteLLMExceptions:
71
71
  ex = getattr(litellm, var, "default")
72
72
 
73
73
  if ex != "default":
74
+ if not issubclass(ex, BaseException):
75
+ continue
76
+
74
77
  self.exceptions[ex] = self.exception_info[var]
75
78
 
76
79
  def exceptions_tuple(self):