diff --git a/wolf/runner.py b/wolf/runner.py new file mode 100644 index 0000000..54ae4a5 --- /dev/null +++ b/wolf/runner.py @@ -0,0 +1,114 @@ +import logging +import json +import time +from typing import Dict, Any, List, Optional +from .models import ModelFactory +from .gitea import GiteaClient +from .task import Task + +class AgentRunner: + """ + Agent runner for Wolf. + """ + def __init__(self, gitea_client: GiteaClient, config: Dict[str, Any]): + self.gitea = gitea_client + self.config = config + + def execute_task(self, task: Task): + """ + Execute a task through a specified model. + """ + logging.info(f"Executing task {task.id} with model {task.assigned_model} via {task.assigned_provider}") + + # 1. Craft the prompt + prompt = self._craft_prompt(task) + + # 2. Get the model client + provider_config = self.config.get('providers', {}).get(task.assigned_provider, {}) + client = ModelFactory.get_client( + task.assigned_provider, + api_key=provider_config.get('api_key'), + base_url=provider_config.get('base_url') + ) + + # 3. Generate response + try: + response = client.generate(prompt, task.assigned_model, system_prompt="You are an expert software engineer.") + logging.info(f"Generated response for task {task.id}") + except Exception as e: + logging.error(f"Error generating response for task {task.id}: {e}") + return None + + # 4. Parse the response (assuming model returns a JSON or specific format) + # For simplicity, we'll assume the model returns a JSON with 'files' and 'commit_message' + # If not, we'll try to extract it. + try: + # Try to find JSON in the response + if "```json" in response: + json_str = response.split("```json")[1].split("```")[0].strip() + result = json.loads(json_str) + else: + result = json.loads(response) + except Exception as e: + logging.warning(f"Failed to parse JSON from response for task {task.id}: {e}") + # Fallback: Treat entire response as a single file change if possible + result = { + "files": [{"path": "solution.py", "content": response}], + "commit_message": f"Solution for task {task.id}" + } + + # 5. Commit the output back to a feature branch + branch_name = f"wolf-task-{task.id}-{int(time.time())}" + try: + self.gitea.create_branch(task.repo_owner, task.repo_name, branch_name) + for file_change in result.get('files', []): + path = file_change['path'] + content = file_change['content'] + + # Check if file exists to update or create + existing_file = self.gitea.get_file(task.repo_owner, task.repo_name, path) + if existing_file: + self.gitea.update_file( + task.repo_owner, task.repo_name, path, content, + branch_name, existing_file['sha'], + message=result.get('commit_message', f"Update {path} by Wolf") + ) + else: + self.gitea.create_file( + task.repo_owner, task.repo_name, path, content, + branch_name, message=result.get('commit_message', f"Create {path} by Wolf") + ) + + # 6. Create PR + pr_title = f"Wolf Task: {task.title}" + pr_body = f"This PR was generated by Wolf using model {task.assigned_model} via {task.assigned_provider}.\n\nTask Description:\n{task.description}" + pr = self.gitea.create_pull_request(task.repo_owner, task.repo_name, pr_title, pr_body, branch_name) + logging.info(f"Created PR {pr['number']} for task {task.id}") + return pr + except Exception as e: + logging.error(f"Error committing changes or creating PR for task {task.id}: {e}") + return None + + def _craft_prompt(self, task: Task): + """ + Craft the prompt for the model. + """ + return f""" +You are an expert software engineer. Your task is to solve the following coding problem: + +Title: {task.title} +Description: {task.description} + +Please provide the solution as a JSON object with the following structure: +{{ + "files": [ + {{ + "path": "path/to/file.py", + "content": "file content here" + }} + ], + "commit_message": "A meaningful commit message" +}} + +Ensure the code is correct, well-documented, and follows best practices. +"""