mini-swe-agent 1.16.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. mini_swe_agent-1.16.0.dist-info/METADATA +314 -0
  2. mini_swe_agent-1.16.0.dist-info/RECORD +62 -0
  3. mini_swe_agent-1.16.0.dist-info/WHEEL +5 -0
  4. mini_swe_agent-1.16.0.dist-info/entry_points.txt +5 -0
  5. mini_swe_agent-1.16.0.dist-info/licenses/LICENSE.md +21 -0
  6. mini_swe_agent-1.16.0.dist-info/top_level.txt +1 -0
  7. minisweagent/__init__.py +83 -0
  8. minisweagent/__main__.py +7 -0
  9. minisweagent/agents/__init__.py +1 -0
  10. minisweagent/agents/default.py +131 -0
  11. minisweagent/agents/interactive.py +153 -0
  12. minisweagent/agents/interactive_textual.py +450 -0
  13. minisweagent/config/README.md +10 -0
  14. minisweagent/config/__init__.py +27 -0
  15. minisweagent/config/default.yaml +157 -0
  16. minisweagent/config/extra/__init__.py +1 -0
  17. minisweagent/config/extra/swebench.yaml +230 -0
  18. minisweagent/config/extra/swebench_roulette.yaml +233 -0
  19. minisweagent/config/extra/swebench_xml.yaml +215 -0
  20. minisweagent/config/github_issue.yaml +146 -0
  21. minisweagent/config/mini.tcss +86 -0
  22. minisweagent/config/mini.yaml +158 -0
  23. minisweagent/config/mini_no_temp.yaml +158 -0
  24. minisweagent/environments/__init__.py +31 -0
  25. minisweagent/environments/docker.py +114 -0
  26. minisweagent/environments/extra/__init__.py +0 -0
  27. minisweagent/environments/extra/bubblewrap.py +112 -0
  28. minisweagent/environments/extra/swerex_docker.py +47 -0
  29. minisweagent/environments/local.py +38 -0
  30. minisweagent/environments/singularity.py +97 -0
  31. minisweagent/models/__init__.py +114 -0
  32. minisweagent/models/anthropic.py +35 -0
  33. minisweagent/models/extra/__init__.py +0 -0
  34. minisweagent/models/extra/roulette.py +61 -0
  35. minisweagent/models/litellm_model.py +100 -0
  36. minisweagent/models/litellm_response_api_model.py +80 -0
  37. minisweagent/models/openrouter_model.py +125 -0
  38. minisweagent/models/portkey_model.py +154 -0
  39. minisweagent/models/portkey_response_api_model.py +74 -0
  40. minisweagent/models/requesty_model.py +119 -0
  41. minisweagent/models/test_models.py +42 -0
  42. minisweagent/models/utils/__init__.py +0 -0
  43. minisweagent/models/utils/cache_control.py +54 -0
  44. minisweagent/models/utils/key_per_thread.py +20 -0
  45. minisweagent/models/utils/openai_utils.py +41 -0
  46. minisweagent/py.typed +0 -0
  47. minisweagent/run/__init__.py +1 -0
  48. minisweagent/run/extra/__init__.py +0 -0
  49. minisweagent/run/extra/config.py +114 -0
  50. minisweagent/run/extra/swebench.py +266 -0
  51. minisweagent/run/extra/swebench_single.py +79 -0
  52. minisweagent/run/extra/utils/__init__.py +0 -0
  53. minisweagent/run/extra/utils/batch_progress.py +178 -0
  54. minisweagent/run/github_issue.py +87 -0
  55. minisweagent/run/hello_world.py +36 -0
  56. minisweagent/run/inspector.py +212 -0
  57. minisweagent/run/mini.py +108 -0
  58. minisweagent/run/mini_extra.py +44 -0
  59. minisweagent/run/utils/__init__.py +0 -0
  60. minisweagent/run/utils/save.py +78 -0
  61. minisweagent/utils/__init__.py +0 -0
  62. minisweagent/utils/log.py +36 -0
@@ -0,0 +1,212 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Simple trajectory inspector for browsing agent conversation trajectories.
4
+
5
+ [not dim]
6
+ More information about the usage: [bold green]https://mini-swe-agent.com/latest/usage/inspector/[/bold green]
7
+ [/not dim]
8
+ """
9
+
10
+ import json
11
+ import os
12
+ from pathlib import Path
13
+
14
+ import typer
15
+ from rich.text import Text
16
+ from textual.app import App, ComposeResult
17
+ from textual.binding import Binding
18
+ from textual.containers import Container, Vertical, VerticalScroll
19
+ from textual.widgets import Footer, Header, Static
20
+
21
+ from minisweagent.agents.interactive_textual import _messages_to_steps
22
+
23
+ app = typer.Typer(rich_markup_mode="rich", add_completion=False)
24
+
25
+
26
+ class TrajectoryInspector(App):
27
+ BINDINGS = [
28
+ Binding("right,l", "next_step", "Step++"),
29
+ Binding("left,h", "previous_step", "Step--"),
30
+ Binding("0", "first_step", "Step=0"),
31
+ Binding("$", "last_step", "Step=-1"),
32
+ Binding("j,down", "scroll_down", "Scroll down"),
33
+ Binding("k,up", "scroll_up", "Scroll up"),
34
+ Binding("L", "next_trajectory", "Next trajectory"),
35
+ Binding("H", "previous_trajectory", "Previous trajectory"),
36
+ Binding("q", "quit", "Quit"),
37
+ ]
38
+
39
+ def __init__(self, trajectory_files: list[Path]):
40
+ css_path = os.environ.get(
41
+ "MSWEA_INSPECTOR_STYLE_PATH", str(Path(__file__).parent.parent / "config" / "mini.tcss")
42
+ )
43
+ self.__class__.CSS = Path(css_path).read_text()
44
+
45
+ super().__init__()
46
+ self.trajectory_files = trajectory_files
47
+ self._i_trajectory = 0
48
+ self._i_step = 0
49
+ self.messages = []
50
+ self.steps = []
51
+
52
+ if trajectory_files:
53
+ self._load_current_trajectory()
54
+
55
+ # --- Basics ---
56
+
57
+ @property
58
+ def i_step(self) -> int:
59
+ """Current step index."""
60
+ return self._i_step
61
+
62
+ @i_step.setter
63
+ def i_step(self, value: int) -> None:
64
+ """Set current step index, automatically clamping to valid bounds."""
65
+ if value != self._i_step and self.n_steps > 0:
66
+ self._i_step = max(0, min(value, self.n_steps - 1))
67
+ self.query_one(VerticalScroll).scroll_to(y=0, animate=False)
68
+ self.update_content()
69
+
70
+ @property
71
+ def n_steps(self) -> int:
72
+ """Number of steps in current trajectory."""
73
+ return len(self.steps)
74
+
75
+ @property
76
+ def i_trajectory(self) -> int:
77
+ """Current trajectory index."""
78
+ return self._i_trajectory
79
+
80
+ @i_trajectory.setter
81
+ def i_trajectory(self, value: int) -> None:
82
+ """Set current trajectory index, automatically clamping to valid bounds."""
83
+ if value != self._i_trajectory and self.n_trajectories > 0:
84
+ self._i_trajectory = max(0, min(value, self.n_trajectories - 1))
85
+ self._load_current_trajectory()
86
+ self.query_one(VerticalScroll).scroll_to(y=0, animate=False)
87
+ self.update_content()
88
+
89
+ @property
90
+ def n_trajectories(self) -> int:
91
+ """Number of trajectory files."""
92
+ return len(self.trajectory_files)
93
+
94
+ def _load_current_trajectory(self) -> None:
95
+ """Load the currently selected trajectory file."""
96
+ if not self.trajectory_files:
97
+ self.messages = []
98
+ self.steps = []
99
+ return
100
+
101
+ trajectory_file = self.trajectory_files[self.i_trajectory]
102
+ try:
103
+ data = json.loads(trajectory_file.read_text())
104
+
105
+ if isinstance(data, list):
106
+ self.messages = data
107
+ elif isinstance(data, dict) and "messages" in data:
108
+ self.messages = data["messages"]
109
+ else:
110
+ raise ValueError("Unrecognized trajectory format")
111
+
112
+ self.steps = _messages_to_steps(self.messages)
113
+ self._i_step = 0
114
+ except (json.JSONDecodeError, FileNotFoundError, ValueError) as e:
115
+ self.messages = []
116
+ self.steps = []
117
+ self.notify(f"Error loading {trajectory_file.name}: {e}", severity="error")
118
+
119
+ @property
120
+ def current_trajectory_name(self) -> str:
121
+ """Get the name of the current trajectory file."""
122
+ if not self.trajectory_files:
123
+ return "No trajectories"
124
+ return self.trajectory_files[self.i_trajectory].name
125
+
126
+ def compose(self) -> ComposeResult:
127
+ yield Header()
128
+ with Container(id="main"):
129
+ with VerticalScroll():
130
+ yield Vertical(id="content")
131
+ yield Footer()
132
+
133
+ def on_mount(self) -> None:
134
+ self.update_content()
135
+
136
+ def update_content(self) -> None:
137
+ """Update the displayed content."""
138
+ container = self.query_one("#content", Vertical)
139
+ container.remove_children()
140
+
141
+ if not self.steps:
142
+ container.mount(Static("No trajectory loaded or empty trajectory"))
143
+ self.title = "Trajectory Inspector - No Data"
144
+ return
145
+
146
+ for message in self.steps[self.i_step]:
147
+ if isinstance(message["content"], list):
148
+ content_str = "\n".join([item["text"] for item in message["content"]])
149
+ else:
150
+ content_str = str(message["content"])
151
+ message_container = Vertical(classes="message-container")
152
+ container.mount(message_container)
153
+ role = message["role"].replace("assistant", "mini-swe-agent")
154
+ message_container.mount(Static(role.upper(), classes="message-header"))
155
+ message_container.mount(Static(Text(content_str, no_wrap=False), classes="message-content"))
156
+
157
+ self.title = (
158
+ f"Trajectory {self.i_trajectory + 1}/{self.n_trajectories} - "
159
+ f"{self.current_trajectory_name} - "
160
+ f"Step {self.i_step + 1}/{self.n_steps}"
161
+ )
162
+
163
+ # --- Navigation actions ---
164
+
165
+ def action_next_step(self) -> None:
166
+ self.i_step += 1
167
+
168
+ def action_previous_step(self) -> None:
169
+ self.i_step -= 1
170
+
171
+ def action_first_step(self) -> None:
172
+ self.i_step = 0
173
+
174
+ def action_last_step(self) -> None:
175
+ self.i_step = self.n_steps - 1
176
+
177
+ def action_next_trajectory(self) -> None:
178
+ self.i_trajectory += 1
179
+
180
+ def action_previous_trajectory(self) -> None:
181
+ self.i_trajectory -= 1
182
+
183
+ def action_scroll_down(self) -> None:
184
+ vs = self.query_one(VerticalScroll)
185
+ vs.scroll_to(y=vs.scroll_target_y + 15)
186
+
187
+ def action_scroll_up(self) -> None:
188
+ vs = self.query_one(VerticalScroll)
189
+ vs.scroll_to(y=vs.scroll_target_y - 15)
190
+
191
+
192
+ @app.command(help=__doc__)
193
+ def main(
194
+ path: str = typer.Argument(".", help="Directory to search for trajectory files or specific trajectory file"),
195
+ ) -> None:
196
+ path_obj = Path(path)
197
+
198
+ if path_obj.is_file():
199
+ trajectory_files = [path_obj]
200
+ elif path_obj.is_dir():
201
+ trajectory_files = sorted(path_obj.rglob("*.traj.json"))
202
+ if not trajectory_files:
203
+ raise typer.BadParameter(f"No trajectory files found in '{path}'")
204
+ else:
205
+ raise typer.BadParameter(f"Error: Path '{path}' does not exist")
206
+
207
+ inspector = TrajectoryInspector(trajectory_files)
208
+ inspector.run()
209
+
210
+
211
+ if __name__ == "__main__":
212
+ app()
@@ -0,0 +1,108 @@
1
+ #!/usr/bin/env python3
2
+
3
+ """Run mini-SWE-agent in your local environment. This is the default executable `mini`."""
4
+ # Read this first: https://mini-swe-agent.com/latest/usage/mini/ (usage)
5
+
6
+ import os
7
+ import traceback
8
+ from pathlib import Path
9
+ from typing import Any
10
+
11
+ import typer
12
+ import yaml
13
+ from prompt_toolkit.formatted_text import HTML
14
+ from prompt_toolkit.history import FileHistory
15
+ from prompt_toolkit.shortcuts import PromptSession
16
+ from rich.console import Console
17
+
18
+ from minisweagent import global_config_dir
19
+ from minisweagent.agents.interactive import InteractiveAgent
20
+ from minisweagent.agents.interactive_textual import TextualAgent
21
+ from minisweagent.config import builtin_config_dir, get_config_path
22
+ from minisweagent.environments.local import LocalEnvironment
23
+ from minisweagent.models import get_model
24
+ from minisweagent.run.extra.config import configure_if_first_time
25
+ from minisweagent.run.utils.save import save_traj
26
+ from minisweagent.utils.log import logger
27
+
28
+ DEFAULT_CONFIG = Path(os.getenv("MSWEA_MINI_CONFIG_PATH", builtin_config_dir / "mini.yaml"))
29
+ DEFAULT_OUTPUT = global_config_dir / "last_mini_run.traj.json"
30
+ console = Console(highlight=False)
31
+ app = typer.Typer(rich_markup_mode="rich")
32
+ prompt_session = PromptSession(history=FileHistory(global_config_dir / "mini_task_history.txt"))
33
+ _HELP_TEXT = """Run mini-SWE-agent in your local environment.
34
+
35
+ [not dim]
36
+ There are two different user interfaces:
37
+
38
+ [bold green]mini[/bold green] Simple REPL-style interface
39
+ [bold green]mini -v[/bold green] Pager-style interface (Textual)
40
+
41
+ More information about the usage: [bold green]https://mini-swe-agent.com/latest/usage/mini/[/bold green]
42
+ [/not dim]
43
+ """
44
+
45
+
46
+ # fmt: off
47
+ @app.command(help=_HELP_TEXT)
48
+ def main(
49
+ visual: bool = typer.Option(False, "-v", "--visual", help="Toggle (pager-style) UI (Textual) depending on the MSWEA_VISUAL_MODE_DEFAULT environment setting",),
50
+ model_name: str | None = typer.Option( None, "-m", "--model", help="Model to use",),
51
+ model_class: str | None = typer.Option(None, "--model-class", help="Model class to use (e.g., 'anthropic' or 'minisweagent.models.anthropic.AnthropicModel')", rich_help_panel="Advanced"),
52
+ task: str | None = typer.Option(None, "-t", "--task", help="Task/problem statement", show_default=False),
53
+ yolo: bool = typer.Option(False, "-y", "--yolo", help="Run without confirmation"),
54
+ cost_limit: float | None = typer.Option(None, "-l", "--cost-limit", help="Cost limit. Set to 0 to disable."),
55
+ config_spec: Path = typer.Option(DEFAULT_CONFIG, "-c", "--config", help="Path to config file"),
56
+ output: Path | None = typer.Option(DEFAULT_OUTPUT, "-o", "--output", help="Output trajectory file"),
57
+ exit_immediately: bool = typer.Option( False, "--exit-immediately", help="Exit immediately when the agent wants to finish instead of prompting.", rich_help_panel="Advanced"),
58
+ ) -> Any:
59
+ # fmt: on
60
+ configure_if_first_time()
61
+ config_path = get_config_path(config_spec)
62
+ console.print(f"Loading agent config from [bold green]'{config_path}'[/bold green]")
63
+ config = yaml.safe_load(config_path.read_text())
64
+
65
+ if not task:
66
+ console.print("[bold yellow]What do you want to do?")
67
+ task = prompt_session.prompt(
68
+ "",
69
+ multiline=True,
70
+ bottom_toolbar=HTML(
71
+ "Submit task: <b fg='yellow' bg='black'>Esc+Enter</b> | "
72
+ "Navigate history: <b fg='yellow' bg='black'>Arrow Up/Down</b> | "
73
+ "Search history: <b fg='yellow' bg='black'>Ctrl+R</b>"
74
+ ),
75
+ )
76
+ console.print("[bold green]Got that, thanks![/bold green]")
77
+
78
+ if yolo:
79
+ config.setdefault("agent", {})["mode"] = "yolo"
80
+ if cost_limit is not None:
81
+ config.setdefault("agent", {})["cost_limit"] = cost_limit
82
+ if exit_immediately:
83
+ config.setdefault("agent", {})["confirm_exit"] = False
84
+ if model_class is not None:
85
+ config.setdefault("model", {})["model_class"] = model_class
86
+ model = get_model(model_name, config.get("model", {}))
87
+ env = LocalEnvironment(**config.get("env", {}))
88
+
89
+ # Both visual flag and the MSWEA_VISUAL_MODE_DEFAULT flip the mode, so it's essentially a XOR
90
+ agent_class = InteractiveAgent
91
+ if visual == (os.getenv("MSWEA_VISUAL_MODE_DEFAULT", "false") == "false"):
92
+ agent_class = TextualAgent
93
+
94
+ agent = agent_class(model, env, **config.get("agent", {}))
95
+ exit_status, result, extra_info = None, None, None
96
+ try:
97
+ exit_status, result = agent.run(task) # type: ignore[arg-type]
98
+ except Exception as e:
99
+ logger.error(f"Error running agent: {e}", exc_info=True)
100
+ exit_status, result = type(e).__name__, str(e)
101
+ extra_info = {"traceback": traceback.format_exc()}
102
+ finally:
103
+ save_traj(agent, output, exit_status=exit_status, result=result, extra_info=extra_info) # type: ignore[arg-type]
104
+ return agent
105
+
106
+
107
+ if __name__ == "__main__":
108
+ app()
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env python3
2
+
3
+ import sys
4
+ from importlib import import_module
5
+
6
+ from rich.console import Console
7
+
8
+ subcommands = [
9
+ ("minisweagent.run.extra.config", ["config"], "Manage the global config file"),
10
+ ("minisweagent.run.inspector", ["inspect", "i", "inspector"], "Run inspector (browse trajectories)"),
11
+ ("minisweagent.run.github_issue", ["github-issue", "gh"], "Run on a GitHub issue"),
12
+ ("minisweagent.run.extra.swebench", ["swebench"], "Evaluate on SWE-bench (batch mode)"),
13
+ ("minisweagent.run.extra.swebench_single", ["swebench-single"], "Evaluate on SWE-bench (single instance)"),
14
+ ]
15
+
16
+
17
+ def get_docstring() -> str:
18
+ lines = [
19
+ "This is the [yellow]central entry point for all extra commands[/yellow] from mini-swe-agent.",
20
+ "",
21
+ "Available sub-commands:",
22
+ "",
23
+ ]
24
+ for _, aliases, description in subcommands:
25
+ alias_text = " or ".join(f"[bold green]{alias}[/bold green]" for alias in aliases)
26
+ lines.append(f" {alias_text}: {description}")
27
+ return "\n".join(lines)
28
+
29
+
30
+ def main():
31
+ args = sys.argv[1:]
32
+
33
+ if len(args) == 0 or len(args) == 1 and args[0] in ["-h", "--help"]:
34
+ return Console().print(get_docstring())
35
+
36
+ for module_path, aliases, _ in subcommands:
37
+ if args[0] in aliases:
38
+ return import_module(module_path).app(args[1:], prog_name=f"mini-extra {aliases[0]}")
39
+
40
+ return Console().print(get_docstring())
41
+
42
+
43
+ if __name__ == "__main__":
44
+ main()
File without changes
@@ -0,0 +1,78 @@
1
+ import dataclasses
2
+ import json
3
+ from collections.abc import Callable
4
+ from pathlib import Path
5
+ from typing import Any
6
+
7
+ from minisweagent import Agent, __version__
8
+
9
+
10
+ def _get_class_name_with_module(obj: Any) -> str:
11
+ """Get the full class name with module path."""
12
+ return f"{obj.__class__.__module__}.{obj.__class__.__name__}"
13
+
14
+
15
+ def _asdict(obj: Any) -> dict:
16
+ """Convert config objects to dicts."""
17
+ if dataclasses.is_dataclass(obj):
18
+ return dataclasses.asdict(obj) # type: ignore[arg-type]
19
+ return obj # let's try our luck
20
+
21
+
22
+ def save_traj(
23
+ agent: Agent | None,
24
+ path: Path | None,
25
+ *,
26
+ print_path: bool = True,
27
+ exit_status: str | None = None,
28
+ result: str | None = None,
29
+ extra_info: dict | None = None,
30
+ print_fct: Callable = print,
31
+ **kwargs,
32
+ ):
33
+ """Save the trajectory of the agent to a file.
34
+
35
+ Args:
36
+ agent: The agent to save the trajectory of.
37
+ path: The path to save the trajectory to.
38
+ print_path: Whether to print confirmation of path to the terminal.
39
+ exit_status: The exit status of the agent.
40
+ result: The result/submission of the agent.
41
+ extra_info: Extra information to save (will be merged into the info dict).
42
+ **kwargs: Additional information to save (will be merged into top level)
43
+
44
+ """
45
+ if path is None:
46
+ return
47
+ data = {
48
+ "info": {
49
+ "exit_status": exit_status,
50
+ "submission": result,
51
+ "model_stats": {
52
+ "instance_cost": 0.0,
53
+ "api_calls": 0,
54
+ },
55
+ "mini_version": __version__,
56
+ },
57
+ "messages": [],
58
+ "trajectory_format": "mini-swe-agent-1",
59
+ } | kwargs
60
+ if agent is not None:
61
+ data["info"]["model_stats"]["instance_cost"] = agent.model.cost
62
+ data["info"]["model_stats"]["api_calls"] = agent.model.n_calls
63
+ data["messages"] = agent.messages
64
+ data["info"]["config"] = {
65
+ "agent": _asdict(agent.config),
66
+ "model": _asdict(agent.model.config),
67
+ "environment": _asdict(agent.env.config),
68
+ "agent_type": _get_class_name_with_module(agent),
69
+ "model_type": _get_class_name_with_module(agent.model),
70
+ "environment_type": _get_class_name_with_module(agent.env),
71
+ }
72
+ if extra_info:
73
+ data["info"].update(extra_info)
74
+
75
+ path.parent.mkdir(parents=True, exist_ok=True)
76
+ path.write_text(json.dumps(data, indent=2))
77
+ if print_path:
78
+ print_fct(f"Saved trajectory to '{path}'")
File without changes
@@ -0,0 +1,36 @@
1
+ import logging
2
+ from pathlib import Path
3
+
4
+ from rich.logging import RichHandler
5
+
6
+
7
+ def _setup_root_logger() -> None:
8
+ logger = logging.getLogger("minisweagent")
9
+ logger.setLevel(logging.DEBUG)
10
+ _handler = RichHandler(
11
+ show_path=False,
12
+ show_time=False,
13
+ show_level=False,
14
+ markup=True,
15
+ )
16
+ _formatter = logging.Formatter("%(name)s: %(levelname)s: %(message)s")
17
+ _handler.setFormatter(_formatter)
18
+ logger.addHandler(_handler)
19
+
20
+
21
+ def add_file_handler(path: Path | str, level: int = logging.DEBUG, *, print_path: bool = True) -> None:
22
+ logger = logging.getLogger("minisweagent")
23
+ handler = logging.FileHandler(path)
24
+ handler.setLevel(level)
25
+ formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
26
+ handler.setFormatter(formatter)
27
+ logger.addHandler(handler)
28
+ if print_path:
29
+ print(f"Logging to '{path}'")
30
+
31
+
32
+ _setup_root_logger()
33
+ logger = logging.getLogger("minisweagent")
34
+
35
+
36
+ __all__ = ["logger"]