Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ swe-agent = [
"unidiff>=0.7",
]
gcp = [
"cooperbench",
"google-cloud-batch>=0.17",
"google-cloud-compute>=1.0",
"google-cloud-storage>=2.0",
Expand Down Expand Up @@ -186,7 +187,7 @@ exclude_lines = [
]

[tool.uv.sources]
cooperbench = { workspace = true }
openhands-sdk = { path = "src/cooperbench/agents/openhands_agent_sdk/openhands-sdk", editable = true }
openhands-tools = { path = "src/cooperbench/agents/openhands_agent_sdk/openhands-tools", editable = true }
openhands-workspace = { path = "src/cooperbench/agents/openhands_agent_sdk/openhands-workspace", editable = true }
cooperbench = { workspace = true }
1 change: 1 addition & 0 deletions src/cooperbench/agents/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ def run(
# Add your agent's shorthand here when registering a new adapter
AGENT_SHORTHANDS = {
"mini_swe_agent": "msa",
"mini_swe_agent_v2": "msa_v2",
"swe_agent": "sw",
"openhands_sdk": "oh",
}
Expand Down
3 changes: 1 addition & 2 deletions src/cooperbench/agents/mini_swe_agent/environments/modal.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,7 @@ def _get_or_build_image(image_name: str) -> modal.Image:
if image_name in _image_cache:
return _image_cache[image_name]

# Build and cache (CACHE_BUST env forces new image hash) TODO: @arpan remove this after testing
image = modal.Image.from_registry(image_name).entrypoint([]).env({"COOPERBENCH_CACHE": "v6"})
image = modal.Image.from_registry(image_name).entrypoint([])
_image_cache[image_name] = image
return image

Expand Down
102 changes: 102 additions & 0 deletions src/cooperbench/agents/mini_swe_agent_v2/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
"""
mini-swe-agent v2 - A Minimal AI Agent for Software Engineering (Tool-calling version)

Source: https://github.com/SWE-agent/mini-swe-agent
Version: 2.1.0 (commit 56613dd)
License: MIT
Copyright (c) 2025 Kilian A. Lieret and Carlos E. Jimenez

This code is copied directly from mini-swe-agent with modifications:
- Import paths changed from 'minisweagent' to 'cooperbench.agents.mini_swe_agent_v2'
- Removed text-based (regex) action parsing (v1 compat), keeping only tool calling
- Removed interactive agent, non-Docker environments, non-litellm models
- Added inter-agent communication (messaging + git connectors) for CooperBench

Citation:
@misc{minisweagent2025,
title={mini-swe-agent: A Minimal AI Agent for Software Engineering},
author={Lieret, Kilian A. and Jimenez, Carlos E.},
year={2025},
url={https://github.com/SWE-agent/mini-swe-agent}
}

This file provides:
- Path settings for global config file & relative directories
- Version numbering
- Protocols for the core components of mini-swe-agent.
"""

__version__ = "2.1.0"

import os
from pathlib import Path
from typing import Any, Protocol

import dotenv
from platformdirs import user_config_dir

from cooperbench.agents.mini_swe_agent_v2.utils.log import logger

package_dir = Path(__file__).resolve().parent

global_config_dir = Path(os.getenv("MSWEA_GLOBAL_CONFIG_DIR") or user_config_dir("mini-swe-agent"))
global_config_dir.mkdir(parents=True, exist_ok=True)
global_config_file = Path(global_config_dir) / ".env"

dotenv.load_dotenv(dotenv_path=global_config_file)


# === Protocols ===
# You can ignore them unless you want static type checking.


class Model(Protocol):
"""Protocol for language models."""

config: Any

def query(self, messages: list[dict[str, str]], **kwargs) -> dict: ...

def format_message(self, **kwargs) -> dict: ...

def format_observation_messages(
self, message: dict, outputs: list[dict], template_vars: dict | None = None
) -> list[dict]: ...

def get_template_vars(self, **kwargs) -> dict[str, Any]: ...

def serialize(self) -> dict: ...


class Environment(Protocol):
"""Protocol for execution environments."""

config: Any

def execute(self, action: dict, cwd: str = "") -> dict[str, Any]: ...

def get_template_vars(self, **kwargs) -> dict[str, Any]: ...

def serialize(self) -> dict: ...


class Agent(Protocol):
"""Protocol for agents."""

config: Any

def run(self, task: str, **kwargs) -> dict: ...

def save(self, path: Path | None, *extra_dicts) -> dict: ...


__all__ = [
"Agent",
"Model",
"Environment",
"package_dir",
"__version__",
"global_config_file",
"global_config_dir",
"logger",
]
145 changes: 145 additions & 0 deletions src/cooperbench/agents/mini_swe_agent_v2/adapter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
"""Mini-SWE-Agent v2 adapter for CooperBench.

This adapter wraps the mini-swe-agent v2 framework (tool-calling version)
to conform to the AgentRunner interface used by CooperBench.
"""

import yaml

from cooperbench.agents import AgentResult
from cooperbench.agents.mini_swe_agent_v2.agents.default import DefaultAgent
from cooperbench.agents.mini_swe_agent_v2.config import get_config_path
from cooperbench.agents.mini_swe_agent_v2.connectors import GitConnector
from cooperbench.agents.mini_swe_agent_v2.connectors.messaging import MessagingConnector
from cooperbench.agents.mini_swe_agent_v2.models.litellm_model import LitellmModel
from cooperbench.agents.mini_swe_agent_v2.models.utils.actions_toolcall import SEND_MESSAGE_TOOL
from cooperbench.agents.registry import register


@register("mini_swe_agent_v2")
class MiniSweAgentV2Runner:
"""Adapter for mini-swe-agent v2 framework (tool-calling)."""

def run(
self,
task: str,
image: str,
*,
agent_id: str = "agent",
model_name: str = "gpt-4o",
agents: list[str] | None = None,
comm_url: str | None = None,
git_server_url: str | None = None,
git_enabled: bool = False,
messaging_enabled: bool = True,
config: dict | None = None,
agent_config: str | None = None,
log_dir: str | None = None,
) -> AgentResult:
"""Run mini-swe-agent v2 on a task."""
# Always load default config, then merge with any overrides
config_path = get_config_path("mini")
with open(config_path) as f:
default_config = yaml.safe_load(f)

# Merge passed config overrides into default config
if config is not None:
default_config.update(config)

agent_cfg = default_config.get("agent", {})
model_cfg = default_config.get("model", {})
env_cfg = default_config.get("environment", {})
backend = default_config.get("backend", "modal")

# Create environment based on backend
env_kwargs = {
"image": image,
"cwd": "/workspace/repo",
"timeout": 3600,
}
if env_cfg.get("env"):
env_kwargs["env"] = env_cfg["env"]

if backend == "docker":
from cooperbench.agents.mini_swe_agent_v2.environments.docker import DockerEnvironment

if config and config.get("git_network"):
env_kwargs["network"] = config["git_network"]
env = DockerEnvironment(**env_kwargs)
else:
from cooperbench.agents.mini_swe_agent_v2.environments.modal import ModalEnvironment

env = ModalEnvironment(**env_kwargs)

# Capture base commit for patch generation
base_commit_result = env.execute({"command": "git rev-parse HEAD"})
base_commit = base_commit_result.get("output", "").strip()

# Setup messaging connector if enabled
comm = None
use_messaging = messaging_enabled and comm_url and agents and len(agents) > 1
if use_messaging:
comm = MessagingConnector(agent_id=agent_id, agents=agents, url=comm_url)

# Create LLM model with send_message tool if messaging is enabled
extra_tools = [SEND_MESSAGE_TOOL] if use_messaging else None
model = LitellmModel(model_name=model_name, extra_tools=extra_tools, **model_cfg)

# Setup git connector if enabled
if git_enabled and git_server_url and agents:
git_connector = GitConnector(
agent_id=agent_id,
agents=agents,
server_url=git_server_url,
)
git_connector.setup(env)

# Create agent with template variables for collaboration
extra_vars = {
"agent_id": agent_id if (agents and len(agents) > 1) else None,
"agents": agents if agents else [],
"git_enabled": git_enabled,
"messaging_enabled": messaging_enabled,
}

agent = DefaultAgent(
model=model,
env=env,
comm=comm,
agent_id=agent_id,
**agent_cfg,
)
agent.extra_template_vars.update(extra_vars)

# Run agent
error_msg = None
try:
result = agent.run(task=task)
status = result.get("exit_status", "Submitted")
except Exception as e:
status = "Error"
error_msg = str(e)

# Extract patch (committed + uncommitted changes)
patch = self._get_patch(env, base_commit)

# Cleanup
env.cleanup()

return AgentResult(
status=status,
patch=patch,
cost=agent.cost,
steps=agent.n_calls,
messages=agent.messages,
sent_messages=agent.sent_messages,
error=error_msg,
)

def _get_patch(self, env, base_commit: str) -> str:
"""Extract git diff from base commit to current working tree state."""
try:
result = env.execute({"command": f"git diff {base_commit}"})
return result.get("output", "").strip()
except Exception:
return ""
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Agent implementations for mini-SWE-agent v2."""
Loading
Loading