mini-swe-agent 1.10.0__tar.gz → 1.11.0__tar.gz

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 (61) hide show
  1. {mini_swe_agent-1.10.0/src/mini_swe_agent.egg-info → mini_swe_agent-1.11.0}/PKG-INFO +1 -1
  2. {mini_swe_agent-1.10.0 → mini_swe_agent-1.11.0/src/mini_swe_agent.egg-info}/PKG-INFO +1 -1
  3. {mini_swe_agent-1.10.0 → mini_swe_agent-1.11.0}/src/mini_swe_agent.egg-info/SOURCES.txt +1 -0
  4. {mini_swe_agent-1.10.0 → mini_swe_agent-1.11.0}/src/minisweagent/__init__.py +1 -1
  5. {mini_swe_agent-1.10.0 → mini_swe_agent-1.11.0}/src/minisweagent/agents/default.py +4 -2
  6. {mini_swe_agent-1.10.0 → mini_swe_agent-1.11.0}/src/minisweagent/environments/docker.py +3 -1
  7. {mini_swe_agent-1.10.0 → mini_swe_agent-1.11.0}/src/minisweagent/models/__init__.py +1 -0
  8. {mini_swe_agent-1.10.0 → mini_swe_agent-1.11.0}/src/minisweagent/models/litellm_model.py +3 -0
  9. mini_swe_agent-1.11.0/src/minisweagent/models/openrouter_model.py +118 -0
  10. {mini_swe_agent-1.10.0 → mini_swe_agent-1.11.0}/src/minisweagent/run/extra/config.py +4 -1
  11. {mini_swe_agent-1.10.0 → mini_swe_agent-1.11.0}/src/minisweagent/run/extra/swebench.py +15 -6
  12. {mini_swe_agent-1.10.0 → mini_swe_agent-1.11.0}/src/minisweagent/run/github_issue.py +5 -0
  13. {mini_swe_agent-1.10.0 → mini_swe_agent-1.11.0}/src/minisweagent/run/mini.py +8 -4
  14. {mini_swe_agent-1.10.0 → mini_swe_agent-1.11.0}/LICENSE.md +0 -0
  15. {mini_swe_agent-1.10.0 → mini_swe_agent-1.11.0}/README.md +0 -0
  16. {mini_swe_agent-1.10.0 → mini_swe_agent-1.11.0}/pyproject.toml +0 -0
  17. {mini_swe_agent-1.10.0 → mini_swe_agent-1.11.0}/setup.cfg +0 -0
  18. {mini_swe_agent-1.10.0 → mini_swe_agent-1.11.0}/src/mini_swe_agent.egg-info/dependency_links.txt +0 -0
  19. {mini_swe_agent-1.10.0 → mini_swe_agent-1.11.0}/src/mini_swe_agent.egg-info/entry_points.txt +0 -0
  20. {mini_swe_agent-1.10.0 → mini_swe_agent-1.11.0}/src/mini_swe_agent.egg-info/requires.txt +0 -0
  21. {mini_swe_agent-1.10.0 → mini_swe_agent-1.11.0}/src/mini_swe_agent.egg-info/top_level.txt +0 -0
  22. {mini_swe_agent-1.10.0 → mini_swe_agent-1.11.0}/src/minisweagent/__main__.py +0 -0
  23. {mini_swe_agent-1.10.0 → mini_swe_agent-1.11.0}/src/minisweagent/agents/__init__.py +0 -0
  24. {mini_swe_agent-1.10.0 → mini_swe_agent-1.11.0}/src/minisweagent/agents/interactive.py +0 -0
  25. {mini_swe_agent-1.10.0 → mini_swe_agent-1.11.0}/src/minisweagent/agents/interactive_textual.py +0 -0
  26. {mini_swe_agent-1.10.0 → mini_swe_agent-1.11.0}/src/minisweagent/config/README.md +0 -0
  27. {mini_swe_agent-1.10.0 → mini_swe_agent-1.11.0}/src/minisweagent/config/__init__.py +0 -0
  28. {mini_swe_agent-1.10.0 → mini_swe_agent-1.11.0}/src/minisweagent/config/default.yaml +0 -0
  29. {mini_swe_agent-1.10.0 → mini_swe_agent-1.11.0}/src/minisweagent/config/extra/__init__.py +0 -0
  30. {mini_swe_agent-1.10.0 → mini_swe_agent-1.11.0}/src/minisweagent/config/extra/swebench.yaml +0 -0
  31. {mini_swe_agent-1.10.0 → mini_swe_agent-1.11.0}/src/minisweagent/config/extra/swebench_roulette.yaml +0 -0
  32. {mini_swe_agent-1.10.0 → mini_swe_agent-1.11.0}/src/minisweagent/config/github_issue.yaml +0 -0
  33. {mini_swe_agent-1.10.0 → mini_swe_agent-1.11.0}/src/minisweagent/config/mini.tcss +0 -0
  34. {mini_swe_agent-1.10.0 → mini_swe_agent-1.11.0}/src/minisweagent/config/mini.yaml +0 -0
  35. {mini_swe_agent-1.10.0 → mini_swe_agent-1.11.0}/src/minisweagent/config/mini_no_temp.yaml +0 -0
  36. {mini_swe_agent-1.10.0 → mini_swe_agent-1.11.0}/src/minisweagent/environments/__init__.py +0 -0
  37. {mini_swe_agent-1.10.0 → mini_swe_agent-1.11.0}/src/minisweagent/environments/extra/__init__.py +0 -0
  38. {mini_swe_agent-1.10.0 → mini_swe_agent-1.11.0}/src/minisweagent/environments/extra/bubblewrap.py +0 -0
  39. {mini_swe_agent-1.10.0 → mini_swe_agent-1.11.0}/src/minisweagent/environments/extra/swerex_docker.py +0 -0
  40. {mini_swe_agent-1.10.0 → mini_swe_agent-1.11.0}/src/minisweagent/environments/local.py +0 -0
  41. {mini_swe_agent-1.10.0 → mini_swe_agent-1.11.0}/src/minisweagent/environments/singularity.py +0 -0
  42. {mini_swe_agent-1.10.0 → mini_swe_agent-1.11.0}/src/minisweagent/models/anthropic.py +0 -0
  43. {mini_swe_agent-1.10.0 → mini_swe_agent-1.11.0}/src/minisweagent/models/extra/__init__.py +0 -0
  44. {mini_swe_agent-1.10.0 → mini_swe_agent-1.11.0}/src/minisweagent/models/extra/roulette.py +0 -0
  45. {mini_swe_agent-1.10.0 → mini_swe_agent-1.11.0}/src/minisweagent/models/test_models.py +0 -0
  46. {mini_swe_agent-1.10.0 → mini_swe_agent-1.11.0}/src/minisweagent/models/utils/__init__.py +0 -0
  47. {mini_swe_agent-1.10.0 → mini_swe_agent-1.11.0}/src/minisweagent/models/utils/cache_control.py +0 -0
  48. {mini_swe_agent-1.10.0 → mini_swe_agent-1.11.0}/src/minisweagent/models/utils/key_per_thread.py +0 -0
  49. {mini_swe_agent-1.10.0 → mini_swe_agent-1.11.0}/src/minisweagent/py.typed +0 -0
  50. {mini_swe_agent-1.10.0 → mini_swe_agent-1.11.0}/src/minisweagent/run/__init__.py +0 -0
  51. {mini_swe_agent-1.10.0 → mini_swe_agent-1.11.0}/src/minisweagent/run/extra/__init__.py +0 -0
  52. {mini_swe_agent-1.10.0 → mini_swe_agent-1.11.0}/src/minisweagent/run/extra/swebench_single.py +0 -0
  53. {mini_swe_agent-1.10.0 → mini_swe_agent-1.11.0}/src/minisweagent/run/extra/utils/__init__.py +0 -0
  54. {mini_swe_agent-1.10.0 → mini_swe_agent-1.11.0}/src/minisweagent/run/extra/utils/batch_progress.py +0 -0
  55. {mini_swe_agent-1.10.0 → mini_swe_agent-1.11.0}/src/minisweagent/run/hello_world.py +0 -0
  56. {mini_swe_agent-1.10.0 → mini_swe_agent-1.11.0}/src/minisweagent/run/inspector.py +0 -0
  57. {mini_swe_agent-1.10.0 → mini_swe_agent-1.11.0}/src/minisweagent/run/mini_extra.py +0 -0
  58. {mini_swe_agent-1.10.0 → mini_swe_agent-1.11.0}/src/minisweagent/run/utils/__init__.py +0 -0
  59. {mini_swe_agent-1.10.0 → mini_swe_agent-1.11.0}/src/minisweagent/run/utils/save.py +0 -0
  60. {mini_swe_agent-1.10.0 → mini_swe_agent-1.11.0}/src/minisweagent/utils/__init__.py +0 -0
  61. {mini_swe_agent-1.10.0 → mini_swe_agent-1.11.0}/src/minisweagent/utils/log.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mini-swe-agent
3
- Version: 1.10.0
3
+ Version: 1.11.0
4
4
  Summary: Nano SWE Agent - A simple AI software engineering agent
5
5
  Author-email: Kilian Lieret <kilian.lieret@posteo.de>, "Carlos E. Jimenez" <carlosej@princeton.edu>
6
6
  License: MIT License
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mini-swe-agent
3
- Version: 1.10.0
3
+ Version: 1.11.0
4
4
  Summary: Nano SWE Agent - A simple AI software engineering agent
5
5
  Author-email: Kilian Lieret <kilian.lieret@posteo.de>, "Carlos E. Jimenez" <carlosej@princeton.edu>
6
6
  License: MIT License
@@ -34,6 +34,7 @@ src/minisweagent/environments/extra/swerex_docker.py
34
34
  src/minisweagent/models/__init__.py
35
35
  src/minisweagent/models/anthropic.py
36
36
  src/minisweagent/models/litellm_model.py
37
+ src/minisweagent/models/openrouter_model.py
37
38
  src/minisweagent/models/test_models.py
38
39
  src/minisweagent/models/extra/__init__.py
39
40
  src/minisweagent/models/extra/roulette.py
@@ -8,7 +8,7 @@ This file provides:
8
8
  unless you want the static type checking.
9
9
  """
10
10
 
11
- __version__ = "1.10.0"
11
+ __version__ = "1.11.0"
12
12
 
13
13
  import os
14
14
  from pathlib import Path
@@ -5,7 +5,7 @@ import subprocess
5
5
  from collections.abc import Callable
6
6
  from dataclasses import asdict, dataclass
7
7
 
8
- from jinja2 import Template
8
+ from jinja2 import StrictUndefined, Template
9
9
 
10
10
  from minisweagent import Environment, Model
11
11
 
@@ -63,7 +63,9 @@ class DefaultAgent:
63
63
 
64
64
  def render_template(self, template: str, **kwargs) -> str:
65
65
  template_vars = asdict(self.config) | self.env.get_template_vars() | self.model.get_template_vars()
66
- return Template(template).render(**kwargs, **template_vars, **self.extra_template_vars)
66
+ return Template(template, undefined=StrictUndefined).render(
67
+ **kwargs, **template_vars, **self.extra_template_vars
68
+ )
67
69
 
68
70
  def add_message(self, role: str, content: str, **kwargs):
69
71
  self.messages.append({"role": role, "content": content, **kwargs})
@@ -29,6 +29,8 @@ class DockerEnvironmentConfig:
29
29
  """
30
30
  container_timeout: str = "2h"
31
31
  """Max duration to keep container running. Uses the same format as the sleep command."""
32
+ pull_timeout: int = 120
33
+ """Timeout in seconds for pulling images."""
32
34
 
33
35
 
34
36
  class DockerEnvironment:
@@ -65,7 +67,7 @@ class DockerEnvironment:
65
67
  cmd,
66
68
  capture_output=True,
67
69
  text=True,
68
- timeout=120, # docker pull might take a while
70
+ timeout=self.config.pull_timeout, # docker pull might take a while
69
71
  check=True,
70
72
  )
71
73
  self.logger.info(f"Started container {container_name} with ID {result.stdout.strip()}")
@@ -74,6 +74,7 @@ def get_model_name(input_model_name: str | None = None, config: dict | None = No
74
74
  _MODEL_CLASS_MAPPING = {
75
75
  "anthropic": "minisweagent.models.anthropic.AnthropicModel",
76
76
  "litellm": "minisweagent.models.litellm_model.LitellmModel",
77
+ "openrouter": "minisweagent.models.openrouter_model.OpenRouterModel",
77
78
  "deterministic": "minisweagent.models.test_models.DeterministicModel",
78
79
  }
79
80
 
@@ -75,6 +75,9 @@ class LitellmModel:
75
75
  GLOBAL_MODEL_STATS.add(cost)
76
76
  return {
77
77
  "content": response.choices[0].message.content or "", # type: ignore
78
+ "extra": {
79
+ "response": response,
80
+ },
78
81
  }
79
82
 
80
83
  def get_template_vars(self) -> dict[str, Any]:
@@ -0,0 +1,118 @@
1
+ import json
2
+ import logging
3
+ import os
4
+ from dataclasses import asdict, dataclass, field
5
+ from typing import Any
6
+
7
+ import requests
8
+ from tenacity import (
9
+ before_sleep_log,
10
+ retry,
11
+ retry_if_not_exception_type,
12
+ stop_after_attempt,
13
+ wait_exponential,
14
+ )
15
+
16
+ from minisweagent.models import GLOBAL_MODEL_STATS
17
+
18
+ logger = logging.getLogger("openrouter_model")
19
+
20
+
21
+ @dataclass
22
+ class OpenRouterModelConfig:
23
+ model_name: str
24
+ model_kwargs: dict[str, Any] = field(default_factory=dict)
25
+
26
+
27
+ class OpenRouterAPIError(Exception):
28
+ """Custom exception for OpenRouter API errors."""
29
+
30
+ pass
31
+
32
+
33
+ class OpenRouterAuthenticationError(Exception):
34
+ """Custom exception for OpenRouter authentication errors."""
35
+
36
+ pass
37
+
38
+
39
+ class OpenRouterRateLimitError(Exception):
40
+ """Custom exception for OpenRouter rate limit errors."""
41
+
42
+ pass
43
+
44
+
45
+ class OpenRouterModel:
46
+ def __init__(self, **kwargs):
47
+ self.config = OpenRouterModelConfig(**kwargs)
48
+ self.cost = 0.0
49
+ self.n_calls = 0
50
+ self._api_url = "https://openrouter.ai/api/v1/chat/completions"
51
+ self._api_key = os.getenv("OPENROUTER_API_KEY", "")
52
+
53
+ @retry(
54
+ stop=stop_after_attempt(10),
55
+ wait=wait_exponential(multiplier=1, min=4, max=60),
56
+ before_sleep=before_sleep_log(logger, logging.WARNING),
57
+ retry=retry_if_not_exception_type(
58
+ (
59
+ OpenRouterAuthenticationError,
60
+ KeyboardInterrupt,
61
+ )
62
+ ),
63
+ )
64
+ def _query(self, messages: list[dict[str, str]], **kwargs):
65
+ headers = {
66
+ "Authorization": f"Bearer {self._api_key}",
67
+ "Content-Type": "application/json",
68
+ }
69
+
70
+ payload = {
71
+ "model": self.config.model_name,
72
+ "messages": messages,
73
+ "usage": {"include": True},
74
+ **(self.config.model_kwargs | kwargs),
75
+ }
76
+
77
+ try:
78
+ response = requests.post(self._api_url, headers=headers, data=json.dumps(payload), timeout=60)
79
+ response.raise_for_status()
80
+ return response.json()
81
+ except requests.exceptions.HTTPError as e:
82
+ if response.status_code == 401:
83
+ error_msg = "Authentication failed. You can permanently set your API key with `mini-extra config set OPENROUTER_API_KEY YOUR_KEY`."
84
+ raise OpenRouterAuthenticationError(error_msg) from e
85
+ elif response.status_code == 429:
86
+ raise OpenRouterRateLimitError("Rate limit exceeded") from e
87
+ else:
88
+ raise OpenRouterAPIError(f"HTTP {response.status_code}: {response.text}") from e
89
+ except requests.exceptions.RequestException as e:
90
+ raise OpenRouterAPIError(f"Request failed: {e}") from e
91
+
92
+ def query(self, messages: list[dict[str, str]], **kwargs) -> dict:
93
+ response = self._query(messages, **kwargs)
94
+
95
+ # Extract cost from usage information
96
+ usage = response.get("usage", {})
97
+ cost = usage.get("cost", 0.0)
98
+
99
+ # If total_cost is not available, raise an error
100
+ if cost == 0.0:
101
+ raise OpenRouterAPIError(
102
+ f"No cost information available from OpenRouter API for model {self.config.model_name}. "
103
+ "Cost tracking is required but not provided by the API response."
104
+ )
105
+
106
+ self.n_calls += 1
107
+ self.cost += cost
108
+ GLOBAL_MODEL_STATS.add(cost)
109
+
110
+ return {
111
+ "content": response["choices"][0]["message"]["content"] or "",
112
+ "extra": {
113
+ "response": response,
114
+ },
115
+ }
116
+
117
+ def get_template_vars(self) -> dict[str, Any]:
118
+ return asdict(self.config) | {"n_model_calls": self.n_calls, "model_cost": self.cost}
@@ -33,8 +33,11 @@ This setup will ask you for your model and an API key.
33
33
 
34
34
  Here's a few popular models and the required API keys:
35
35
 
36
- [bold green]claude-sonnet-4-20250514[/bold green] ([bold green]ANTHROPIC_API_KEY[/bold green])
36
+ [bold green]anthropic/claude-sonnet-4-20250514[/bold green] ([bold green]ANTHROPIC_API_KEY[/bold green])
37
37
  [bold green]openai/gpt-5[/bold green] or [bold green]openai/gpt-5-mini[/bold green] ([bold green]OPENAI_API_KEY[/bold green])
38
+ [bold green]gemini/gemini-2.5-pro[/bold green] ([bold green]GEMINI_API_KEY[/bold green])
39
+
40
+ [bold]Note: Please always include the provider in the model name.[/bold]
38
41
 
39
42
  [bold yellow]You can leave any setting blank to skip it.[/bold yellow]
40
43
 
@@ -15,6 +15,7 @@ from pathlib import Path
15
15
  import typer
16
16
  import yaml
17
17
  from datasets import load_dataset
18
+ from jinja2 import StrictUndefined, Template
18
19
  from rich.live import Live
19
20
 
20
21
  from minisweagent import Environment
@@ -72,17 +73,25 @@ def get_swebench_docker_image_name(instance: dict) -> str:
72
73
  # Docker doesn't allow double underscore, so we replace them with a magic token
73
74
  iid = instance["instance_id"]
74
75
  id_docker_compatible = iid.replace("__", "_1776_")
75
- image_name = f"swebench/sweb.eval.x86_64.{id_docker_compatible}:latest".lower()
76
+ image_name = f"docker.io/swebench/sweb.eval.x86_64.{id_docker_compatible}:latest".lower()
76
77
  return image_name
77
78
 
78
79
 
79
80
  def get_sb_environment(config: dict, instance: dict) -> Environment:
80
- image_name = get_swebench_docker_image_name(instance)
81
81
  env_config = config.setdefault("environment", {})
82
- if env_config.get("environment_class") == "singularity":
83
- image_name = "docker://" + image_name
84
- env_config["image"] = image_name
85
- return get_environment(env_config, default_type="docker")
82
+ env_config["environment_class"] = env_config.get("environment_class", "docker")
83
+ image_name = get_swebench_docker_image_name(instance)
84
+ if env_config["environment_class"] == "docker":
85
+ env_config["image"] = image_name
86
+ elif env_config["environment_class"] == "singularity":
87
+ env_config["image"] = "docker://" + image_name
88
+ env = get_environment(env_config)
89
+ if startup_command := config.get("run", {}).get("env_startup_command"):
90
+ startup_command = Template(startup_command, undefined=StrictUndefined).render(**instance)
91
+ out = env.execute(startup_command)
92
+ if out["returncode"] != 0:
93
+ raise RuntimeError(f"Error executing startup command: {out}")
94
+ return env
86
95
 
87
96
 
88
97
  def update_preds_file(output_path: Path, instance_id: str, model_name: str, result: str):
@@ -37,13 +37,16 @@ def fetch_github_issue(issue_url: str) -> str:
37
37
  return f"GitHub Issue: {title}\n\n{body}"
38
38
 
39
39
 
40
+ # fmt: off
40
41
  @app.command()
41
42
  def main(
42
43
  issue_url: str = typer.Option(prompt="Enter GitHub issue URL", help="GitHub issue URL"),
43
44
  config: Path = typer.Option(DEFAULT_CONFIG, "-c", "--config", help="Path to config file"),
44
45
  model: str | None = typer.Option(None, "-m", "--model", help="Model to use"),
46
+ 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"),
45
47
  yolo: bool = typer.Option(False, "-y", "--yolo", help="Run without confirmation"),
46
48
  ) -> InteractiveAgent:
49
+ # fmt: on
47
50
  """Run mini-SWE-agent on a GitHub issue"""
48
51
  configure_if_first_time()
49
52
 
@@ -51,6 +54,8 @@ def main(
51
54
  _agent_config = _config.setdefault("agent", {})
52
55
  if yolo:
53
56
  _agent_config["mode"] = "yolo"
57
+ if model_class is not None:
58
+ _config.setdefault("model", {})["model_class"] = model_class
54
59
 
55
60
  task = fetch_github_issue(issue_url)
56
61
 
@@ -48,12 +48,13 @@ More information about the usage: [bold green]https://mini-swe-agent.com/latest/
48
48
  def main(
49
49
  visual: bool = typer.Option(False, "-v", "--visual", help="Toggle (pager-style) UI (Textual) depending on the MSWEA_VISUAL_MODE_DEFAULT environment setting",),
50
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"),
51
52
  task: str | None = typer.Option(None, "-t", "--task", help="Task/problem statement", show_default=False),
52
53
  yolo: bool = typer.Option(False, "-y", "--yolo", help="Run without confirmation"),
53
54
  cost_limit: float | None = typer.Option(None, "-l", "--cost-limit", help="Cost limit. Set to 0 to disable."),
54
55
  config_spec: Path = typer.Option(DEFAULT_CONFIG, "-c", "--config", help="Path to config file"),
55
56
  output: Path | None = typer.Option(DEFAULT_OUTPUT, "-o", "--output", help="Output trajectory file"),
56
- exit_immediately: bool = typer.Option( False, "--exit-immediately", help="Exit immediately when the agent wants to finish instead of prompting."),
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"),
57
58
  ) -> Any:
58
59
  # fmt: on
59
60
  configure_if_first_time()
@@ -72,11 +73,14 @@ def main(
72
73
  )
73
74
  console.print("[bold green]Got that, thanks![/bold green]")
74
75
 
75
- config["agent"]["mode"] = "confirm" if not yolo else "yolo"
76
+ if yolo:
77
+ config.setdefault("agent", {})["mode"] = "yolo"
76
78
  if cost_limit:
77
- config["agent"]["cost_limit"] = cost_limit
79
+ config.setdefault("agent", {})["cost_limit"] = cost_limit
78
80
  if exit_immediately:
79
- config["agent"]["confirm_exit"] = False
81
+ config.setdefault("agent", {})["confirm_exit"] = False
82
+ if model_class is not None:
83
+ config.setdefault("model", {})["model_class"] = model_class
80
84
  model = get_model(model_name, config.get("model", {}))
81
85
  env = LocalEnvironment(**config.get("env", {}))
82
86