File size: 5,134 Bytes
0913c52
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
"""
This is the new version of the coder tool, which uses aider as a command line tool.
"""

import re
import shutil
import subprocess
import tempfile

from loguru import logger

from .registry import register_tool, register_toolset_desc

register_toolset_desc(
    "coder",
    "Coder toolset. This toolset allows you to call an AI coding agent using natural language instructions. "
    "The agent can modify, create, or edit code files based on your instructions. "
    "But you have to provide the details, like the file structure, the file names, the file paths, the file content, etc."
    "Note: The coding agent has no memory of previous interactions and operates independently on each call. And it will not chat and exit the session after it's done.",
)


@register_tool(
    "coder",
    {
        "type": "function",
        "function": {
            "name": "run_coder",
            "description": (
                "Execute a coding task using an AI coding agent (aider). "
                "This tool calls another agent with natural language instructions to modify, create, or edit code files. "
                "But you have to provide the details, like the file structure, the file names, the file paths, the file content, etc."
                "IMPORTANT: The coding agent has no memory of previous interactions - each call is independent. "
                "Provide clear, complete instructions for each task."
                "And the coding agent will not chat and exit the session after it's done."
            ),
            "parameters": {
                "type": "object",
                "properties": {
                    "fnames": {
                        "type": "array",
                        "items": {"type": "string"},
                        "description": (
                            "Optional list of file paths or directory paths to add to the coding session. "
                            "The agent will have access to read and modify these files. "
                            "If not provided, the agent will work without pre-loaded files."
                        ),
                    },
                    "instruction": {
                        "type": "string",
                        "description": (
                            "Natural language instruction for the coding agent. "
                            "Be specific and clear about what you want the agent to do. "
                            "Example: 'Create a new function called calculate_sum that adds two numbers'"
                        ),
                    },
                },
                "required": ["instruction"],
            },
        },
    },
)
def run_coder(fnames: list[str] | None = None, instruction: str = "") -> str:
    """
    Execute a coding task using aider.

    Args:
        fnames: Optional list of file paths to include in the coding session
        instruction: Natural language instruction for the coding agent

    Returns:
        Result message from the coding agent
    """

    # Use OS command line to call `aider` and pass the instruction via stdin
    logger.debug("Running aider with fnames: \n{}\n\ninstruction: \n{}", fnames, instruction)
    try:
        if not instruction.strip():
            return "Error: instruction must be a non-empty string."
        aider_path = shutil.which("aider")
        if not aider_path:
            err_text = "Error: 'aider' executable not found in PATH. Please install aider and ensure it is available."
            logger.error(err_text)
            return err_text

        # create temp file to store the instruction
        with tempfile.NamedTemporaryFile(mode="w") as temp_file:
            temp_file.write(instruction)
            temp_file.flush()

            temp_file_path = temp_file.name

            cmd = [aider_path, "--message-file", temp_file_path, "--yes", "--exit"]

            if fnames:
                cmd.extend(fnames)

            result = subprocess.run(
                cmd,
                text=True,
                capture_output=True,
            )

            if result.returncode != 0:
                return "Error executing coding task: " + (
                    result.stderr.strip() or f"Non-zero exit status {result.returncode}"
                )

            output_text = result.stdout.strip()
            output_text = _parse_aider_output(output_text)

            return output_text or "Coding task completed."

    except Exception as e:
        return f"Error executing coding task: {str(e)}"


def _parse_aider_output(output_text: str) -> str:
    """Parse aider output to extract the result message."""
    lines = output_text.splitlines()
    result = ""
    for i, line in enumerate(lines):
        line = line.strip()
        if line.startswith("Repo-map:"):
            result = "\n".join(lines[i + 1 :])
            break
    else:
        result = "\n".join(lines)

    result = result.strip()

    # remove all the color codes in the results
    ansi_escape = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])")
    result = ansi_escape.sub("", result)

    return result