Files
agents/CoderAgent.py
T
2026-09-19 13:18:49 +00:00

116 lines
6.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.
class CoderAgent:
"""Агент-исполнитель: выполняет подзадачу, генерирует код и применяет изменения."""
@staticmethod
def execute(project: Project, subtask: str, files: List[str], error_feedback: str = "") -> bool:
print(f"💻 Кодер выполняет: {subtask}")
# 1. Начинаем с переданных файлов
context_files = set(files)
# 2. Извлекаем пути из error_feedback (если есть)
if error_feedback:
import re
# Ищем пути к файлам (с расширениями .ts, .tsx, .js, .jsx, .html, .css, .py)
# Паттерн ищет строки, содержащие путь с расширением, например "src/App.tsx"
path_pattern = r'[^\s"\']+\.(tsx?|jsx?|html|css|py)\b'
found_paths = re.findall(path_pattern, error_feedback, re.IGNORECASE)
# Также ищем полные URL типа file:// или http:// с путем
url_pattern = r'(?:file://|https?://[^\s]+)(/[^\s"\']+)'
url_matches = re.findall(url_pattern, error_feedback)
for match in url_matches:
# обрезаем до относительного пути относительно корня проекта?
# Пока просто добавляем как есть, но лучше нормализовать
found_paths.append(match)
# Добавляем найденные пути, если они не в списке
for p in found_paths:
# Удаляем лишние символы, например, :line:col или суффиксы
# Часто путь может быть вида "file:///home/project/src/App.tsx:12:34"
# или просто "src/App.tsx:12:34"
p = p.split(':')[0] # отбрасываем строку/колонку, если есть
p = p.strip()
# Если путь начинается с /, то это абсолютный путь, пытаемся сделать относительным
if p.startswith('/'):
p = p.lstrip('/')
# Если это абсолютный путь внутри проекта, то можно обрезать корень
# Упрощённо: ищем вхождение папки проекта
proj_root = str(project.path.resolve())
if p.startswith(proj_root):
p = os.path.relpath(p, proj_root)
else:
# иначе оставляем как есть (может не сработать)
pass
if p and p not in context_files:
context_files.add(p)
# 3. Читаем содержимое всех файлов
file_context = []
for rel_path in context_files:
full_path = project.path / rel_path
if not full_path.exists():
print(f" ⚠️ Файл не найден: {rel_path}")
continue
content = project.read_file_content(full_path)
if content:
file_context.append(f"=== {rel_path} ===\n{content}")
else:
print(f" ⚠️ Не удалось прочитать {rel_path}")
context = "\n".join(file_context) if file_context else "(нет файлов для контекста)"
# 4. Формируем промпт
system_prompt = "Ты — опытный разработчик. Отвечай только кодом, без лишнего текста. Если нужно изменить несколько файлов, выдавай блоки вида: [filename]\n```language\nкод\n```"
user_prompt = f"""
Текущая задача: {subtask}
{f"При предыдущей попытке возникли ошибки: {error_feedback}" if error_feedback else ""}
Текущий код указанных файлов:
{context}
Внеси изменения в соответствующие файлы. Если создаёшь новый файл — укажи его путь относительно корня проекта.
Выдай изменения в формате:
[путь/к/файлу1]
```язык
код
```
[путь/к/файлу2]
```язык
код
```
"""
messages = [{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt}]
response = project.ollama_client.chat(messages, temperature=0.2)
if not response:
print("⚠️ Ответ от Ollama пустой.")
return False
try:
pattern = r'\[(.*?)\]\s*```(?:\w+)?\s*(.*?)```'
matches = re.findall(pattern, response, re.DOTALL)
if not matches:
print("⚠️ Не удалось распарсить ответ, пытаемся сохранить как один файл")
if context_files:
first_file = next(iter(context_files))
full_path = project.path / first_file
project.write_file(full_path, response)
print(f" Записан {first_file} (весь ответ)")
return True
if files:
first_file = files[0]
full_path = project.path / first_file
project.write_file(full_path, response)
print(f" Записан {first_file} (весь ответ)")
return True
return False
for file_path_str, content in matches:
file_path_str = file_path_str.strip().strip('"').strip("'")
target_path = project.path / file_path_str
project.write_file(target_path, content.strip())
print(f" Обновлён {target_path.relative_to(project.path)}")
return True
except Exception as e:
print(f" Ошибка при парсинге ответа кодера: {e}")
return False