Codeki / backend /agent.py
Mayank2027's picture
Update backend/agent.py
155186b verified
Raw
History Blame Contribute Delete
5.4 kB
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