|
| 1 | +"""Mini-SWE-Agent v2 adapter for CooperBench. |
| 2 | +
|
| 3 | +This adapter wraps the mini-swe-agent v2 framework (tool-calling version) |
| 4 | +to conform to the AgentRunner interface used by CooperBench. |
| 5 | +""" |
| 6 | + |
| 7 | +import yaml |
| 8 | + |
| 9 | +from cooperbench.agents import AgentResult |
| 10 | +from cooperbench.agents.mini_swe_agent_v2.agents.default import DefaultAgent |
| 11 | +from cooperbench.agents.mini_swe_agent_v2.config import get_config_path |
| 12 | +from cooperbench.agents.mini_swe_agent_v2.connectors import GitConnector |
| 13 | +from cooperbench.agents.mini_swe_agent_v2.connectors.messaging import MessagingConnector |
| 14 | +from cooperbench.agents.mini_swe_agent_v2.models.litellm_model import LitellmModel |
| 15 | +from cooperbench.agents.mini_swe_agent_v2.models.utils.actions_toolcall import SEND_MESSAGE_TOOL |
| 16 | +from cooperbench.agents.registry import register |
| 17 | + |
| 18 | + |
| 19 | +@register("mini_swe_agent_v2") |
| 20 | +class MiniSweAgentV2Runner: |
| 21 | + """Adapter for mini-swe-agent v2 framework (tool-calling).""" |
| 22 | + |
| 23 | + def run( |
| 24 | + self, |
| 25 | + task: str, |
| 26 | + image: str, |
| 27 | + *, |
| 28 | + agent_id: str = "agent", |
| 29 | + model_name: str = "gpt-4o", |
| 30 | + agents: list[str] | None = None, |
| 31 | + comm_url: str | None = None, |
| 32 | + git_server_url: str | None = None, |
| 33 | + git_enabled: bool = False, |
| 34 | + messaging_enabled: bool = True, |
| 35 | + config: dict | None = None, |
| 36 | + agent_config: str | None = None, |
| 37 | + log_dir: str | None = None, |
| 38 | + ) -> AgentResult: |
| 39 | + """Run mini-swe-agent v2 on a task.""" |
| 40 | + # Always load default config, then merge with any overrides |
| 41 | + config_path = get_config_path("mini") |
| 42 | + with open(config_path) as f: |
| 43 | + default_config = yaml.safe_load(f) |
| 44 | + |
| 45 | + # Merge passed config overrides into default config |
| 46 | + if config is not None: |
| 47 | + default_config.update(config) |
| 48 | + |
| 49 | + agent_cfg = default_config.get("agent", {}) |
| 50 | + model_cfg = default_config.get("model", {}) |
| 51 | + env_cfg = default_config.get("environment", {}) |
| 52 | + backend = default_config.get("backend", "modal") |
| 53 | + |
| 54 | + # Create environment based on backend |
| 55 | + env_kwargs = { |
| 56 | + "image": image, |
| 57 | + "cwd": "/workspace/repo", |
| 58 | + "timeout": 3600, |
| 59 | + } |
| 60 | + if env_cfg.get("env"): |
| 61 | + env_kwargs["env"] = env_cfg["env"] |
| 62 | + |
| 63 | + if backend == "docker": |
| 64 | + from cooperbench.agents.mini_swe_agent_v2.environments.docker import DockerEnvironment |
| 65 | + |
| 66 | + if config and config.get("git_network"): |
| 67 | + env_kwargs["network"] = config["git_network"] |
| 68 | + env = DockerEnvironment(**env_kwargs) |
| 69 | + else: |
| 70 | + from cooperbench.agents.mini_swe_agent_v2.environments.modal import ModalEnvironment |
| 71 | + |
| 72 | + env = ModalEnvironment(**env_kwargs) |
| 73 | + |
| 74 | + # Capture base commit for patch generation |
| 75 | + base_commit_result = env.execute({"command": "git rev-parse HEAD"}) |
| 76 | + base_commit = base_commit_result.get("output", "").strip() |
| 77 | + |
| 78 | + # Setup messaging connector if enabled |
| 79 | + comm = None |
| 80 | + use_messaging = messaging_enabled and comm_url and agents and len(agents) > 1 |
| 81 | + if use_messaging: |
| 82 | + comm = MessagingConnector(agent_id=agent_id, agents=agents, url=comm_url) |
| 83 | + |
| 84 | + # Create LLM model with send_message tool if messaging is enabled |
| 85 | + extra_tools = [SEND_MESSAGE_TOOL] if use_messaging else None |
| 86 | + model = LitellmModel(model_name=model_name, extra_tools=extra_tools, **model_cfg) |
| 87 | + |
| 88 | + # Setup git connector if enabled |
| 89 | + if git_enabled and git_server_url and agents: |
| 90 | + git_connector = GitConnector( |
| 91 | + agent_id=agent_id, |
| 92 | + agents=agents, |
| 93 | + server_url=git_server_url, |
| 94 | + ) |
| 95 | + git_connector.setup(env) |
| 96 | + |
| 97 | + # Create agent with template variables for collaboration |
| 98 | + extra_vars = { |
| 99 | + "agent_id": agent_id if (agents and len(agents) > 1) else None, |
| 100 | + "agents": agents if agents else [], |
| 101 | + "git_enabled": git_enabled, |
| 102 | + "messaging_enabled": messaging_enabled, |
| 103 | + } |
| 104 | + |
| 105 | + agent = DefaultAgent( |
| 106 | + model=model, |
| 107 | + env=env, |
| 108 | + comm=comm, |
| 109 | + agent_id=agent_id, |
| 110 | + **agent_cfg, |
| 111 | + ) |
| 112 | + agent.extra_template_vars.update(extra_vars) |
| 113 | + |
| 114 | + # Run agent |
| 115 | + error_msg = None |
| 116 | + try: |
| 117 | + result = agent.run(task=task) |
| 118 | + status = result.get("exit_status", "Submitted") |
| 119 | + except Exception as e: |
| 120 | + status = "Error" |
| 121 | + error_msg = str(e) |
| 122 | + |
| 123 | + # Extract patch (committed + uncommitted changes) |
| 124 | + patch = self._get_patch(env, base_commit) |
| 125 | + |
| 126 | + # Cleanup |
| 127 | + env.cleanup() |
| 128 | + |
| 129 | + return AgentResult( |
| 130 | + status=status, |
| 131 | + patch=patch, |
| 132 | + cost=agent.cost, |
| 133 | + steps=agent.n_calls, |
| 134 | + messages=agent.messages, |
| 135 | + sent_messages=agent.sent_messages, |
| 136 | + error=error_msg, |
| 137 | + ) |
| 138 | + |
| 139 | + def _get_patch(self, env, base_commit: str) -> str: |
| 140 | + """Extract git diff from base commit to current working tree state.""" |
| 141 | + try: |
| 142 | + result = env.execute({"command": f"git diff {base_commit}"}) |
| 143 | + return result.get("output", "").strip() |
| 144 | + except Exception: |
| 145 | + return "" |
0 commit comments