5959from aider .repo import ANY_GIT_ERROR , GitRepo
6060from aider .repomap import RepoMap
6161from aider .run_cmd import run_cmd
62+ from aider .sessions import SessionManager
6263from aider .utils import format_tokens , is_image_file
6364
6465from ..dump import dump # noqa: F401
@@ -1121,6 +1122,8 @@ async def _run_linear(self, with_message=None, preproc=True):
11211122 self .keyboard_interrupt ()
11221123 except (asyncio .CancelledError , IndexError ):
11231124 pass
1125+
1126+ self .auto_save_session ()
11241127 except EOFError :
11251128 return
11261129 finally :
@@ -1272,6 +1275,8 @@ async def _run_patched(self, with_message=None, preproc=True):
12721275 self .io .stop_spinner ()
12731276
12741277 self .keyboard_interrupt ()
1278+
1279+ self .auto_save_session ()
12751280 except EOFError :
12761281 return
12771282 finally :
@@ -2240,12 +2245,20 @@ def _print_tool_call_info(self, server_tool_calls):
22402245
22412246 for server , tool_calls in server_tool_calls .items ():
22422247 for tool_call in tool_calls :
2243- self .io .tool_output (f"Tool Call: { tool_call .function .name } " )
2248+ color_start = "[blue]" if self .pretty else ""
2249+ color_end = "[/blue]" if self .pretty else ""
22442250
2251+ self .io .tool_output (
2252+ f"{ color_start } Tool Call:{ color_end } { server .name } • { tool_call .function .name } "
2253+ )
22452254 # Parse and format arguments as headers with values
22462255 if tool_call .function .arguments :
22472256 # Only do JSON unwrapping for tools containing "replace" in their name
2248- if "replace" in tool_call .function .name .lower ():
2257+ if (
2258+ "replace" in tool_call .function .name .lower ()
2259+ or "insert" in tool_call .function .name .lower ()
2260+ or "update" in tool_call .function .name .lower ()
2261+ ):
22492262 try :
22502263 args_dict = json .loads (tool_call .function .arguments )
22512264 first_key = True
@@ -2258,7 +2271,7 @@ def _print_tool_call_info(self, server_tool_calls):
22582271 if first_key :
22592272 self .io .tool_output ("\n " )
22602273 first_key = False
2261- self .io .tool_output (f"{ key } :" )
2274+ self .io .tool_output (f"{ color_start } { key } :{ color_end } " )
22622275 # Split the value by newlines and output each line separately
22632276 if isinstance (value , str ):
22642277 for line in value .split ("\n " ):
@@ -2269,13 +2282,11 @@ def _print_tool_call_info(self, server_tool_calls):
22692282 except json .JSONDecodeError :
22702283 # If JSON parsing fails, show raw arguments
22712284 raw_args = tool_call .function .arguments
2272- self .io .tool_output (f"Arguments: { raw_args } " )
2285+ self .io .tool_output (f"{ color_start } Arguments:{ color_end } { raw_args } " )
22732286 else :
22742287 # For non-replace tools, show raw arguments
22752288 raw_args = tool_call .function .arguments
2276- self .io .tool_output (f"Arguments: { raw_args } " )
2277-
2278- self .io .tool_output (f"MCP Server: { server .name } " )
2289+ self .io .tool_output (f"{ color_start } Arguments:{ color_end } { raw_args } " )
22792290
22802291 if self .verbose :
22812292 self .io .tool_output (f"Tool ID: { tool_call .id } " )
@@ -2295,7 +2306,15 @@ def _gather_server_tool_calls(self, tool_calls):
22952306 return None
22962307
22972308 server_tool_calls = {}
2309+ tool_id_set = set ()
2310+
22982311 for tool_call in tool_calls :
2312+ # LLM APIs sometimes return duplicates and that's annoying part 3
2313+ if tool_call .get ("id" ) in tool_id_set :
2314+ continue
2315+
2316+ tool_id_set .add (tool_call .get ("id" ))
2317+
22992318 # Check if this tool_call matches any MCP tool
23002319 for server_name , server_tools in self .mcp_tools :
23012320 for tool in server_tools :
@@ -2343,8 +2362,16 @@ async def _exec_server_tools(server, tool_calls_list):
23432362 try :
23442363 # Connect to the server once
23452364 session = await server .connect ()
2365+ tool_id_set = set ()
2366+
23462367 # Execute all tool calls for this server
23472368 for tool_call in tool_calls_list :
2369+ # LLM APIs sometimes return duplicates and that's annoying part 4
2370+ if tool_call .id in tool_id_set :
2371+ continue
2372+
2373+ tool_id_set .add (tool_call .id )
2374+
23482375 try :
23492376 # Arguments can be a stream of JSON objects.
23502377 # We need to parse them and run a tool call for each.
@@ -3491,6 +3518,17 @@ def apply_edits(self, edits):
34913518 def apply_edits_dry_run (self , edits ):
34923519 return edits
34933520
3521+ def auto_save_session (self ):
3522+ """Automatically save the current session as 'auto-save'."""
3523+ if not getattr (self .args , "auto_save" , False ):
3524+ return
3525+ try :
3526+ session_manager = SessionManager (self , self .io )
3527+ session_manager .save_session ("auto-save" , False )
3528+ except Exception :
3529+ # Don't show errors for auto-save to avoid interrupting the user experience
3530+ pass
3531+
34943532 async def run_shell_commands (self ):
34953533 if not self .suggest_shell_commands :
34963534 return ""
0 commit comments