mini-swe-agent 1.17.5__py3-none-any.whl → 2.0.0a1__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 (73) hide show
  1. {mini_swe_agent-1.17.5.dist-info → mini_swe_agent-2.0.0a1.dist-info}/METADATA +36 -52
  2. mini_swe_agent-2.0.0a1.dist-info/RECORD +70 -0
  3. mini_swe_agent-2.0.0a1.dist-info/entry_points.txt +5 -0
  4. minisweagent/__init__.py +19 -26
  5. minisweagent/agents/default.py +128 -113
  6. minisweagent/agents/interactive.py +119 -58
  7. minisweagent/config/README.md +3 -4
  8. minisweagent/config/__init__.py +36 -1
  9. minisweagent/config/benchmarks/swebench.yaml +156 -0
  10. minisweagent/config/{extra/swebench.yaml → benchmarks/swebench_backticks.yaml} +69 -64
  11. minisweagent/config/benchmarks/swebench_modal.yaml +47 -0
  12. minisweagent/config/{extra → benchmarks}/swebench_xml.yaml +73 -70
  13. minisweagent/config/default.yaml +24 -21
  14. minisweagent/config/inspector.tcss +42 -0
  15. minisweagent/config/mini.yaml +53 -71
  16. minisweagent/config/{github_issue.yaml → mini_textbased.yaml} +43 -29
  17. minisweagent/environments/__init__.py +1 -0
  18. minisweagent/environments/docker.py +67 -20
  19. minisweagent/environments/extra/bubblewrap.py +86 -47
  20. minisweagent/environments/extra/swerex_docker.py +53 -20
  21. minisweagent/environments/extra/swerex_modal.py +90 -0
  22. minisweagent/environments/local.py +62 -21
  23. minisweagent/environments/singularity.py +59 -18
  24. minisweagent/exceptions.py +22 -0
  25. minisweagent/models/__init__.py +6 -7
  26. minisweagent/models/extra/roulette.py +20 -17
  27. minisweagent/models/litellm_model.py +90 -44
  28. minisweagent/models/litellm_response_model.py +80 -0
  29. minisweagent/models/litellm_textbased_model.py +45 -0
  30. minisweagent/models/openrouter_model.py +87 -45
  31. minisweagent/models/openrouter_response_model.py +123 -0
  32. minisweagent/models/openrouter_textbased_model.py +76 -0
  33. minisweagent/models/portkey_model.py +84 -42
  34. minisweagent/models/portkey_response_model.py +163 -0
  35. minisweagent/models/requesty_model.py +91 -41
  36. minisweagent/models/test_models.py +246 -19
  37. minisweagent/models/utils/actions_text.py +60 -0
  38. minisweagent/models/utils/actions_toolcall.py +102 -0
  39. minisweagent/models/utils/actions_toolcall_response.py +110 -0
  40. minisweagent/models/utils/anthropic_utils.py +28 -0
  41. minisweagent/models/utils/cache_control.py +15 -2
  42. minisweagent/models/utils/content_string.py +74 -0
  43. minisweagent/models/utils/openai_multimodal.py +50 -0
  44. minisweagent/models/utils/retry.py +25 -0
  45. minisweagent/run/benchmarks/__init__.py +1 -0
  46. minisweagent/run/{extra → benchmarks}/swebench.py +56 -35
  47. minisweagent/run/{extra → benchmarks}/swebench_single.py +36 -26
  48. minisweagent/run/{extra → benchmarks}/utils/batch_progress.py +1 -1
  49. minisweagent/run/hello_world.py +6 -0
  50. minisweagent/run/mini.py +54 -63
  51. minisweagent/run/utilities/__init__.py +1 -0
  52. minisweagent/run/{extra → utilities}/config.py +2 -0
  53. minisweagent/run/{inspector.py → utilities/inspector.py} +90 -11
  54. minisweagent/run/{mini_extra.py → utilities/mini_extra.py} +9 -5
  55. minisweagent/utils/serialize.py +26 -0
  56. mini_swe_agent-1.17.5.dist-info/RECORD +0 -61
  57. mini_swe_agent-1.17.5.dist-info/entry_points.txt +0 -5
  58. minisweagent/agents/interactive_textual.py +0 -450
  59. minisweagent/config/extra/swebench_roulette.yaml +0 -233
  60. minisweagent/config/mini.tcss +0 -86
  61. minisweagent/models/anthropic.py +0 -35
  62. minisweagent/models/litellm_response_api_model.py +0 -82
  63. minisweagent/models/portkey_response_api_model.py +0 -75
  64. minisweagent/models/utils/key_per_thread.py +0 -20
  65. minisweagent/models/utils/openai_utils.py +0 -41
  66. minisweagent/run/github_issue.py +0 -87
  67. minisweagent/run/utils/__init__.py +0 -0
  68. minisweagent/run/utils/save.py +0 -78
  69. {mini_swe_agent-1.17.5.dist-info → mini_swe_agent-2.0.0a1.dist-info}/WHEEL +0 -0
  70. {mini_swe_agent-1.17.5.dist-info → mini_swe_agent-2.0.0a1.dist-info}/licenses/LICENSE.md +0 -0
  71. {mini_swe_agent-1.17.5.dist-info → mini_swe_agent-2.0.0a1.dist-info}/top_level.txt +0 -0
  72. /minisweagent/config/{extra → benchmarks}/__init__.py +0 -0
  73. /minisweagent/run/{extra → benchmarks}/utils/__init__.py +0 -0
@@ -1,86 +0,0 @@
1
- Screen {
2
- layout: grid;
3
- grid-size: 1;
4
- grid-rows: auto 1fr auto;
5
- }
6
-
7
- #main {
8
- height: 100%;
9
- padding: 1;
10
- layout: vertical;
11
- }
12
-
13
- Footer {
14
- dock: bottom;
15
- content-align: center middle;
16
- }
17
-
18
- #content {
19
- height: auto;
20
- min-height: 0;
21
- }
22
-
23
- .smart-input-container {
24
- height: auto;
25
- margin-top: 0;
26
- padding: 0;
27
- min-height: 0;
28
- }
29
-
30
- .multi-input {
31
- height: auto;
32
- max-height: 20;
33
- min-height: 3;
34
- }
35
-
36
- .prompt-display {
37
- margin-bottom: 1;
38
- padding: 0 1;
39
- text-style: bold;
40
- }
41
-
42
- .hint-text{
43
- margin-top: 1;
44
- margin-bottom: 1;
45
- padding: 0 1;
46
- color: white;
47
- }
48
-
49
- .message-container {
50
- margin: 1;
51
- padding: 1;
52
- background: $surface;
53
- height: auto;
54
- width: 100%;
55
- }
56
-
57
- .message-header {
58
- text-align: left;
59
- color: $primary;
60
- padding: 0 1;
61
- text-style: bold;
62
- }
63
-
64
- .input-request-header {
65
- color: $warning;
66
- }
67
-
68
- .message-content {
69
- margin-top: 1;
70
- padding: 0 1;
71
- }
72
-
73
- Header.running {
74
- background: $error;
75
- }
76
-
77
- .button-container {
78
- layout: horizontal;
79
- align-horizontal: center;
80
- margin-top: 1;
81
- }
82
-
83
- .button-container Button {
84
- margin: 0 1;
85
- min-width: 10;
86
- }
@@ -1,35 +0,0 @@
1
- import os
2
- import warnings
3
- from typing import Literal
4
-
5
- from minisweagent.models.litellm_model import LitellmModel, LitellmModelConfig
6
- from minisweagent.models.utils.cache_control import set_cache_control
7
- from minisweagent.models.utils.key_per_thread import get_key_per_thread
8
-
9
-
10
- class AnthropicModelConfig(LitellmModelConfig):
11
- set_cache_control: Literal["default_end"] | None = "default_end"
12
- """Set explicit cache control markers, for example for Anthropic models"""
13
-
14
-
15
- class AnthropicModel(LitellmModel):
16
- """This class is now only a thin wrapper around the LitellmModel class.
17
- It is largely kept for backwards compatibility.
18
- It will not be selected by `get_model` and `get_model_class` unless explicitly specified.
19
- """
20
-
21
- def __init__(self, *, config_class: type = AnthropicModelConfig, **kwargs):
22
- super().__init__(config_class=config_class, **kwargs)
23
-
24
- def query(self, messages: list[dict], **kwargs) -> dict:
25
- api_key = None
26
- # Legacy only
27
- if rotating_keys := os.getenv("ANTHROPIC_API_KEYS"):
28
- warnings.warn(
29
- "ANTHROPIC_API_KEYS is deprecated and will be removed in the future. "
30
- "Simply use the ANTHROPIC_API_KEY environment variable instead. "
31
- "Key rotation is no longer required."
32
- )
33
- api_key = get_key_per_thread(rotating_keys.split("::"))
34
- messages = set_cache_control(messages, mode="default_end")
35
- return super().query(messages, api_key=api_key, **kwargs)
@@ -1,82 +0,0 @@
1
- import logging
2
- from collections.abc import Callable
3
- from dataclasses import dataclass
4
-
5
- import litellm
6
- from tenacity import (
7
- before_sleep_log,
8
- retry,
9
- retry_if_not_exception_type,
10
- stop_after_attempt,
11
- wait_exponential,
12
- )
13
-
14
- from minisweagent.models.litellm_model import LitellmModel, LitellmModelConfig
15
- from minisweagent.models.utils.openai_utils import coerce_responses_text
16
-
17
- logger = logging.getLogger("litellm_response_api_model")
18
-
19
-
20
- @dataclass
21
- class LitellmResponseAPIModelConfig(LitellmModelConfig):
22
- pass
23
-
24
-
25
- class LitellmResponseAPIModel(LitellmModel):
26
- def __init__(self, *, config_class: Callable = LitellmResponseAPIModelConfig, **kwargs):
27
- super().__init__(config_class=config_class, **kwargs)
28
- self._previous_response_id: str | None = None
29
-
30
- @retry(
31
- reraise=True,
32
- stop=stop_after_attempt(10),
33
- wait=wait_exponential(multiplier=1, min=4, max=60),
34
- before_sleep=before_sleep_log(logger, logging.WARNING),
35
- retry=retry_if_not_exception_type(
36
- (
37
- litellm.exceptions.UnsupportedParamsError,
38
- litellm.exceptions.NotFoundError,
39
- litellm.exceptions.PermissionDeniedError,
40
- litellm.exceptions.ContextWindowExceededError,
41
- litellm.exceptions.APIError,
42
- litellm.exceptions.AuthenticationError,
43
- KeyboardInterrupt,
44
- )
45
- ),
46
- )
47
- def _query(self, messages: list[dict[str, str]], **kwargs):
48
- try:
49
- # Remove 'timestamp' field added by agent - not supported by OpenAI responses API
50
- clean_messages = [{"role": msg["role"], "content": msg["content"]} for msg in messages]
51
- resp = litellm.responses(
52
- model=self.config.model_name,
53
- input=clean_messages if self._previous_response_id is None else clean_messages[-1:],
54
- previous_response_id=self._previous_response_id,
55
- **(self.config.model_kwargs | kwargs),
56
- )
57
- self._previous_response_id = getattr(resp, "id", None)
58
- return resp
59
- except litellm.exceptions.AuthenticationError as e:
60
- e.message += " You can permanently set your API key with `mini-extra config set KEY VALUE`."
61
- raise e
62
-
63
- def query(self, messages: list[dict[str, str]], **kwargs) -> dict:
64
- response = self._query(messages, **kwargs)
65
- text = coerce_responses_text(response)
66
- try:
67
- cost = litellm.cost_calculator.completion_cost(response, model=self.config.model_name)
68
- except Exception as e:
69
- logger.critical(
70
- f"Error calculating cost for model {self.config.model_name}: {e}. "
71
- "Please check the 'Updating the model registry' section in the documentation. "
72
- "http://bit.ly/4p31bi4 Still stuck? Please open a github issue for help!"
73
- )
74
- raise
75
- self.n_calls += 1
76
- self.cost += cost
77
- from minisweagent.models import GLOBAL_MODEL_STATS
78
-
79
- GLOBAL_MODEL_STATS.add(cost)
80
- return {
81
- "content": text,
82
- }
@@ -1,75 +0,0 @@
1
- import logging
2
- import os
3
- from dataclasses import dataclass
4
-
5
- import litellm
6
- from tenacity import (
7
- before_sleep_log,
8
- retry,
9
- retry_if_not_exception_type,
10
- stop_after_attempt,
11
- wait_exponential,
12
- )
13
-
14
- from minisweagent.models import GLOBAL_MODEL_STATS
15
- from minisweagent.models.portkey_model import PortkeyModel, PortkeyModelConfig
16
- from minisweagent.models.utils.cache_control import set_cache_control
17
- from minisweagent.models.utils.openai_utils import coerce_responses_text
18
-
19
- logger = logging.getLogger("portkey_response_api_model")
20
-
21
-
22
- @dataclass
23
- class PortkeyResponseAPIModelConfig(PortkeyModelConfig):
24
- pass
25
-
26
-
27
- class PortkeyResponseAPIModel(PortkeyModel):
28
- def __init__(self, *, config_class: type = PortkeyResponseAPIModelConfig, **kwargs):
29
- super().__init__(config_class=config_class, **kwargs)
30
- self._previous_response_id: str | None = None
31
-
32
- @retry(
33
- reraise=True,
34
- stop=stop_after_attempt(int(os.getenv("MSWEA_MODEL_RETRY_STOP_AFTER_ATTEMPT", "10"))),
35
- wait=wait_exponential(multiplier=1, min=4, max=60),
36
- before_sleep=before_sleep_log(logger, logging.WARNING),
37
- retry=retry_if_not_exception_type((KeyboardInterrupt, TypeError, ValueError)),
38
- )
39
- def _query(self, messages: list[dict[str, str]], **kwargs):
40
- input_messages = messages if self._previous_response_id is None else messages[-1:]
41
- resp = self.client.responses.create(
42
- model=self.config.model_name,
43
- input=input_messages,
44
- previous_response_id=self._previous_response_id,
45
- **(self.config.model_kwargs | kwargs),
46
- )
47
- self._previous_response_id = getattr(resp, "id", None)
48
- return resp
49
-
50
- def query(self, messages: list[dict[str, str]], **kwargs) -> dict:
51
- if self.config.set_cache_control:
52
- messages = set_cache_control(messages, mode=self.config.set_cache_control)
53
- response = self._query(messages, **kwargs)
54
- text = coerce_responses_text(response)
55
- try:
56
- cost = litellm.cost_calculator.completion_cost(response, model=self.config.model_name)
57
- assert cost > 0.0, f"Cost is not positive: {cost}"
58
- except Exception as e:
59
- if self.config.cost_tracking != "ignore_errors":
60
- raise RuntimeError(
61
- f"Error calculating cost for model {self.config.model_name}: {e}. "
62
- "You can ignore this issue from your config file with cost_tracking: 'ignore_errors' or "
63
- "globally with export MSWEA_COST_TRACKING='ignore_errors' to ignore this error. "
64
- ) from e
65
- cost = 0.0
66
- self.n_calls += 1
67
- self.cost += cost
68
- GLOBAL_MODEL_STATS.add(cost)
69
- return {
70
- "content": text,
71
- "extra": {
72
- "response": response.model_dump() if hasattr(response, "model_dump") else {},
73
- "cost": cost,
74
- },
75
- }
@@ -1,20 +0,0 @@
1
- """Utility for anthropic where we need different keys for different parallel
2
- agents to not mess up prompt caching.
3
- """
4
-
5
- import threading
6
- import warnings
7
- from typing import Any
8
-
9
- _THREADS_THAT_USED_API_KEYS: list[Any] = []
10
-
11
-
12
- def get_key_per_thread(api_keys: list[Any]) -> Any:
13
- """Choose key based on thread name. Returns None if no keys are available."""
14
- warnings.warn("get_key_per_thread is deprecated and will be removed in the future")
15
- thread_name = threading.current_thread().name
16
- if thread_name not in _THREADS_THAT_USED_API_KEYS:
17
- _THREADS_THAT_USED_API_KEYS.append(thread_name)
18
- thread_idx = _THREADS_THAT_USED_API_KEYS.index(thread_name)
19
- key_idx = thread_idx % len(api_keys)
20
- return api_keys[key_idx] or None
@@ -1,41 +0,0 @@
1
- import logging
2
- from typing import Any
3
-
4
- from openai.types.responses.response_output_message import ResponseOutputMessage
5
-
6
- logger = logging.getLogger("openai_utils")
7
-
8
-
9
- def coerce_responses_text(resp: Any) -> str:
10
- """Helper to normalize OpenAI Responses API result to text.
11
-
12
- Works with both OpenAI client responses and LiteLLM/Portkey responses.
13
- """
14
- text = getattr(resp, "output_text", None)
15
- if isinstance(text, str) and text:
16
- return text
17
-
18
- try:
19
- output = []
20
- for item in resp.output:
21
- if isinstance(item, dict):
22
- content = item.get("content", [])
23
- elif isinstance(item, ResponseOutputMessage):
24
- content = item.content
25
- else:
26
- continue
27
-
28
- for content_item in content:
29
- if isinstance(content_item, dict):
30
- text_val = content_item.get("text")
31
- elif hasattr(content_item, "text"):
32
- text_val = content_item.text
33
- else:
34
- continue
35
-
36
- if text_val:
37
- output.append(text_val)
38
- return "\n\n".join(output) or ""
39
- except (AttributeError, IndexError, TypeError):
40
- logger.warning(f"Could not extract text from response: {resp}")
41
- return ""
@@ -1,87 +0,0 @@
1
- #!/usr/bin/env python3
2
- import os
3
- from pathlib import Path
4
-
5
- import requests
6
- import typer
7
- import yaml
8
- from rich.console import Console
9
-
10
- from minisweagent.agents.interactive import InteractiveAgent
11
- from minisweagent.config import builtin_config_dir, get_config_path
12
- from minisweagent.environments.docker import DockerEnvironment
13
- from minisweagent.models import get_model
14
- from minisweagent.run.extra.config import configure_if_first_time
15
- from minisweagent.run.utils.save import save_traj
16
-
17
- DEFAULT_CONFIG = Path(os.getenv("MSWEA_GITHUB_CONFIG_PATH", builtin_config_dir / "github_issue.yaml"))
18
- console = Console(highlight=False)
19
- app = typer.Typer(rich_markup_mode="rich", add_completion=False)
20
-
21
-
22
- def fetch_github_issue(issue_url: str) -> str:
23
- """Fetch GitHub issue text from the URL."""
24
- # Convert GitHub issue URL to API URL
25
- api_url = issue_url.replace("github.com", "api.github.com/repos").replace("/issues/", "/issues/")
26
-
27
- headers = {}
28
- if github_token := os.getenv("GITHUB_TOKEN"):
29
- headers["Authorization"] = f"token {github_token}"
30
-
31
- response = requests.get(api_url, headers=headers)
32
- issue_data = response.json()
33
-
34
- title = issue_data["title"]
35
- body = issue_data["body"] or ""
36
-
37
- return f"GitHub Issue: {title}\n\n{body}"
38
-
39
-
40
- # fmt: off
41
- @app.command()
42
- def main(
43
- issue_url: str = typer.Option(prompt="Enter GitHub issue URL", help="GitHub issue URL"),
44
- config: Path = typer.Option(DEFAULT_CONFIG, "-c", "--config", help="Path to config file"),
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"),
47
- yolo: bool = typer.Option(False, "-y", "--yolo", help="Run without confirmation"),
48
- ) -> InteractiveAgent:
49
- # fmt: on
50
- """Run mini-SWE-agent on a GitHub issue"""
51
- configure_if_first_time()
52
-
53
- config_path = get_config_path(config)
54
- console.print(f"Loading agent config from [bold green]'{config_path}'[/bold green]")
55
- _config = yaml.safe_load(config_path.read_text())
56
- _agent_config = _config.setdefault("agent", {})
57
- if yolo:
58
- _agent_config["mode"] = "yolo"
59
- if model_class is not None:
60
- _config.setdefault("model", {})["model_class"] = model_class
61
-
62
- task = fetch_github_issue(issue_url)
63
-
64
- agent = InteractiveAgent(
65
- get_model(model, _config.get("model", {})),
66
- DockerEnvironment(**_config.get("environment", {})),
67
- **_agent_config,
68
- )
69
-
70
- repo_url = issue_url.split("/issues/")[0]
71
- if github_token := os.getenv("GITHUB_TOKEN"):
72
- repo_url = repo_url.replace("https://github.com/", f"https://{github_token}@github.com/") + ".git"
73
-
74
- agent.env.execute(f"git clone {repo_url} /testbed", cwd="/")
75
-
76
- exit_status, result = None, None
77
- try:
78
- exit_status, result = agent.run(task)
79
- except KeyboardInterrupt:
80
- console.print("\n[bold red]KeyboardInterrupt -- goodbye[/bold red]")
81
- finally:
82
- save_traj(agent, Path("traj.json"), exit_status=exit_status, result=result)
83
- return agent
84
-
85
-
86
- if __name__ == "__main__":
87
- app()
File without changes
@@ -1,78 +0,0 @@
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