Execute agent loop until task is complete or max steps reached. Args: cancel_event: Optional asyncio.Event that can be set to cancel execution. When set, the agent will stop at the next safe checkpoint (after completing the cur
(self, cancel_event: Optional[asyncio.Event] = None)
| 319 | return summary_content |
| 320 | |
| 321 | async def run(self, cancel_event: Optional[asyncio.Event] = None) -> str: |
| 322 | """Execute agent loop until task is complete or max steps reached. |
| 323 | |
| 324 | Args: |
| 325 | cancel_event: Optional asyncio.Event that can be set to cancel execution. |
| 326 | When set, the agent will stop at the next safe checkpoint |
| 327 | (after completing the current step to keep messages consistent). |
| 328 | |
| 329 | Returns: |
| 330 | The final response content, or error message (including cancellation message). |
| 331 | """ |
| 332 | # Set cancellation event (can also be set via self.cancel_event before calling run()) |
| 333 | if cancel_event is not None: |
| 334 | self.cancel_event = cancel_event |
| 335 | |
| 336 | # Start new run, initialize log file |
| 337 | self.logger.start_new_run() |
| 338 | print(f"{Colors.DIM}📝 Log file: {self.logger.get_log_file_path()}{Colors.RESET}") |
| 339 | |
| 340 | step = 0 |
| 341 | run_start_time = perf_counter() |
| 342 | |
| 343 | while step < self.max_steps: |
| 344 | # Check for cancellation at start of each step |
| 345 | if self._check_cancelled(): |
| 346 | self._cleanup_incomplete_messages() |
| 347 | cancel_msg = "Task cancelled by user." |
| 348 | print(f"\n{Colors.BRIGHT_YELLOW}⚠️ {cancel_msg}{Colors.RESET}") |
| 349 | return cancel_msg |
| 350 | |
| 351 | step_start_time = perf_counter() |
| 352 | # Check and summarize message history to prevent context overflow |
| 353 | await self._summarize_messages() |
| 354 | |
| 355 | # Step header with proper width calculation |
| 356 | BOX_WIDTH = 58 |
| 357 | step_text = f"{Colors.BOLD}{Colors.BRIGHT_CYAN}💭 Step {step + 1}/{self.max_steps}{Colors.RESET}" |
| 358 | step_display_width = calculate_display_width(step_text) |
| 359 | padding = max(0, BOX_WIDTH - 1 - step_display_width) # -1 for leading space |
| 360 | |
| 361 | print(f"\n{Colors.DIM}╭{'─' * BOX_WIDTH}╮{Colors.RESET}") |
| 362 | print(f"{Colors.DIM}│{Colors.RESET} {step_text}{' ' * padding}{Colors.DIM}│{Colors.RESET}") |
| 363 | print(f"{Colors.DIM}╰{'─' * BOX_WIDTH}╯{Colors.RESET}") |
| 364 | |
| 365 | # Get tool list for LLM call |
| 366 | tool_list = list(self.tools.values()) |
| 367 | |
| 368 | # Log LLM request and call LLM with Tool objects directly |
| 369 | self.logger.log_request(messages=self.messages, tools=tool_list) |
| 370 | |
| 371 | try: |
| 372 | response = await self.llm.generate(messages=self.messages, tools=tool_list) |
| 373 | except Exception as e: |
| 374 | # Check if it's a retry exhausted error |
| 375 | from .retry import RetryExhaustedError |
| 376 | |
| 377 | if isinstance(e, RetryExhaustedError): |
| 378 | error_msg = f"LLM call failed after {e.attempts} retries\nLast error: {str(e.last_exception)}" |