RA.Aid/ra_aid/tools/programmer.py

168 lines
5.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import os
from typing import Dict, List, Union
from langchain_core.tools import tool
from rich.console import Console
from rich.markdown import Markdown
from rich.panel import Panel
from rich.text import Text
from ra_aid.logging_config import get_logger
from ra_aid.proc.interactive import run_interactive_command
from ra_aid.text.processing import truncate_output
from ra_aid.tools.memory import _global_memory, log_work_event
console = Console()
logger = get_logger(__name__)
def _truncate_for_log(text: str, max_length: int = 300) -> str:
"""Truncate text for logging, adding [truncated] if necessary."""
if len(text) <= max_length:
return text
return text[:max_length] + "... [truncated]"
@tool
def run_programming_task(
instructions: str, files: List[str] = []
) -> Dict[str, Union[str, int, bool]]:
"""Assign a programming task to a human programmer. Use this instead of trying to write code to files yourself.
Before using this tool, ensure all related files have been emitted with emit_related_files.
The programmer sees only what you provide, no conversation history.
Give detailed instructions including multi-file tasks but do not write their code.
The programmer cannot run commands.
If new files are created, emit them after finishing.
They can add/modify files, but not remove. Use run_shell_command to remove files. If referencing files youll delete, remove them after they finish.
Use write_file_tool instead if you need to write the entire contents of file(s).
If the programmer wrote files, they actually wrote to disk. You do not need to rewrite the output of what the programmer showed you.
Args:
instructions: REQUIRED Programming task instructions (markdown format, use newlines and as many tokens as needed, no commands allowed)
files: Optional; if not provided, uses related_files
Returns: { "output": stdout+stderr, "return_code": 0 if success, "success": True/False }
"""
# Get related files if no specific files provided
file_paths = (
list(_global_memory["related_files"].values())
if "related_files" in _global_memory
else []
)
# Build command
command = [
"aider",
"--yes-always",
"--no-auto-commits",
"--dark-mode",
"--no-suggest-shell-commands",
"--no-show-release-notes",
"--no-check-update",
]
# Add config file if specified
if "config" in _global_memory and _global_memory["config"].get("aider_config"):
command.extend(["--config", _global_memory["config"]["aider_config"]])
# if environment variable AIDER_FLAGS exists then parse
if "AIDER_FLAGS" in os.environ:
# wrap in try catch in case of any error and log the error
try:
command.extend(parse_aider_flags(os.environ["AIDER_FLAGS"]))
except Exception as e:
print(f"Error parsing AIDER_FLAGS: {e}")
# ensure message aider argument is always present
command.append("-m")
command.append(instructions)
# Add files to command
files_to_use = file_paths + (files or [])
if files_to_use:
command.extend(files_to_use)
# Create a pretty display of what we're doing
task_display = ["## Instructions\n", f"{instructions}\n"]
if files_to_use:
task_display.extend(
["\n## Files\n", *[f"- `{file}`\n" for file in files_to_use]]
)
markdown_content = "".join(task_display)
console.print(
Panel(
Markdown(markdown_content),
title="🤖 Aider Task",
border_style="bright_blue",
)
)
logger.debug(f"command: {command}")
try:
# Run the command interactively
print()
result = run_interactive_command(command)
print()
# Log the programming task
log_work_event(f"Executed programming task: {_truncate_for_log(instructions)}")
# Return structured output
return {
"output": truncate_output(result[0].decode()) if result[0] else "",
"return_code": result[1],
"success": result[1] == 0,
}
except Exception as e:
print()
error_text = Text()
error_text.append("Error running programming task:\n", style="bold red")
error_text.append(str(e), style="red")
console.print(error_text)
return {"output": str(e), "return_code": 1, "success": False}
def parse_aider_flags(aider_flags: str) -> List[str]:
"""Parse a string of aider flags into a list of flags.
Args:
aider_flags: A string containing comma-separated flags, with or without leading dashes.
Can contain spaces around flags and commas.
Returns:
A list of flags with proper '--' prefix.
Examples:
>>> parse_aider_flags("yes-always,dark-mode")
['--yes-always', '--dark-mode']
>>> parse_aider_flags("--yes-always, --dark-mode")
['--yes-always', '--dark-mode']
>>> parse_aider_flags("")
[]
"""
if not aider_flags.strip():
return []
# Split by comma and strip whitespace
flags = [flag.strip() for flag in aider_flags.split(",")]
# Add '--' prefix if not present and filter out empty flags
return [f"--{flag.lstrip('-')}" for flag in flags if flag.strip()]
# Export the functions
__all__ = ["run_programming_task"]