File size: 5,401 Bytes
155186b
993e775
155186b
993e775
155186b
993e775
8ceea4f
993e775
 
 
cc0a6e7
155186b
8ceea4f
155186b
 
8ceea4f
 
 
 
155186b
 
 
 
 
 
 
 
 
9de2000
155186b
 
9de2000
993e775
 
8ceea4f
993e775
9de2000
155186b
8ceea4f
 
9de2000
8ceea4f
 
 
9de2000
 
8ceea4f
 
 
 
 
 
155186b
8ceea4f
 
 
 
 
 
 
 
 
155186b
8ceea4f
 
 
 
 
 
 
155186b
8ceea4f
155186b
8ceea4f
 
 
 
 
 
 
 
 
 
155186b
8ceea4f
 
 
 
 
 
155186b
 
8ceea4f
 
155186b
8ceea4f
 
 
 
 
9de2000
155186b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9de2000
155186b
 
aafc40f
9de2000
 
8ceea4f
993e775
8ceea4f
993e775
8ceea4f
cc0a6e7
 
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
import asyncio, subprocess, re, json
from pathlib import Path
from typing import List, Dict
from model_router import route_chat

class CodingAgent:
    def __init__(self, project_path: str, model: str = "groq-llama-3.3-70b-versatile"):
        self.project_path = Path(project_path)
        self.model = model
        self.status = "idle"
        self.logs = []
        self.pending_changes = []

    async def run_task(self, task: str) -> dict:
        """New agent loop – returns {logs, pending_changes}."""
        self.status = "running"
        self.logs = []
        self.pending_changes = []

        system = f"""You are an AI coding agent. Complete the user's task step by step.
You work in the project folder: {self.project_path}.
Available tools (respond with a JSON object containing "thought", "tool", and "arguments"):
- read_file(filepath)
- write_file(filepath, content)
- edit_file(filepath, search, replace)
- run_command(command)
- list_dir(path)
- task_complete()

Example: {{"thought": "I need to see the code", "tool": "read_file", "arguments": {{"filepath": "main.py"}}}}
Only output the JSON object."""

        messages = [
            {"role": "system", "content": system},
            {"role": "user", "content": task}
        ]

        for step in range(8):
            response = await self._generate(messages)
            self.logs.append(f"Step {step+1}: {response[:150]}...")

            action = self._parse_action(response)
            if not action:
                messages.append({"role": "user", "content": "Invalid JSON. Please output only the JSON action."})
                continue

            thought = action.get("thought", "")
            tool = action.get("tool", "")
            args = action.get("arguments", {})

            result = ""
            if tool == "read_file":
                result = self._read_file(args.get("filepath", ""))
            elif tool == "write_file":
                filepath = args.get("filepath", "")
                content = args.get("content", "")
                self.pending_changes.append({"action": "create", "filepath": filepath, "content": content})
                result = f"Queued creation of {filepath}"
            elif tool == "edit_file":
                filepath = args.get("filepath", "")
                search = args.get("search", "")
                replace = args.get("replace", "")
                current = self._read_file(filepath)
                if search in current:
                    modified = current.replace(search, replace)
                    self.pending_changes.append({"action": "edit", "filepath": filepath, "original": current, "modified": modified})
                    result = f"Queued edit of {filepath}"
                else:
                    result = f"Search string not found in {filepath}"
            elif tool == "run_command":
                result = self._run_command(args.get("command", ""))
            elif tool == "list_dir":
                result = "\n".join(self._list_dir(args.get("path", "")))
            elif tool == "task_complete":
                self.logs.append("Agent marked task complete.")
                break
            else:
                result = f"Unknown tool: {tool}"

            messages.append({"role": "assistant", "content": response})
            messages.append({"role": "user", "content": f"Tool result: {result}"})

        self.status = "awaiting_approval"
        return {"logs": self.logs, "pending_changes": self.pending_changes}

    async def approve_and_apply(self):
        self.status = "applying"
        for change in self.pending_changes:
            try:
                if change["action"] == "create":
                    (self.project_path / change["filepath"]).parent.mkdir(parents=True, exist_ok=True)
                    (self.project_path / change["filepath"]).write_text(change["content"])
                    self.logs.append(f"Created {change['filepath']}")
                elif change["action"] == "edit":
                    (self.project_path / change["filepath"]).write_text(change["modified"])
                    self.logs.append(f"Edited {change['filepath']}")
            except Exception as e:
                self.logs.append(f"Error: {e}")
        self.pending_changes = []
        self.status = "completed"

    def _read_file(self, filepath: str) -> str:
        p = self.project_path / filepath
        return p.read_text() if p.exists() else "File not found."

    def _run_command(self, cmd: str) -> str:
        try:
            r = subprocess.run(cmd, shell=True, cwd=self.project_path, capture_output=True, text=True, timeout=30)
            return r.stdout + r.stderr
        except Exception as e:
            return str(e)

    def _list_dir(self, path: str) -> list:
        p = self.project_path / path if path else self.project_path
        try:
            return [f.name for f in p.iterdir()]
        except:
            return []

    def _parse_action(self, text: str) -> dict | None:
        text = text.strip()
        if text.startswith("```"): text = text[3:]
        if text.endswith("```"): text = text[:-3]
        try:
            return json.loads(text)
        except:
            return None

    async def _generate(self, messages):
        full = ""
        async for token in route_chat(self.model, messages):
            full += token
        return full