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,26 +1,41 @@
1
1
  """Run on a single SWE-Bench instance."""
2
2
 
3
- import traceback
4
3
  from pathlib import Path
5
4
 
6
5
  import typer
7
- import yaml
8
6
  from datasets import load_dataset
9
7
 
10
8
  from minisweagent import global_config_dir
11
9
  from minisweagent.agents.interactive import InteractiveAgent
12
- from minisweagent.config import builtin_config_dir, get_config_path
10
+ from minisweagent.config import builtin_config_dir, get_config_from_spec
13
11
  from minisweagent.models import get_model
14
- from minisweagent.run.extra.swebench import (
12
+ from minisweagent.run.benchmarks.swebench import (
15
13
  DATASET_MAPPING,
16
14
  get_sb_environment,
17
15
  )
18
- from minisweagent.run.utils.save import save_traj
19
16
  from minisweagent.utils.log import logger
17
+ from minisweagent.utils.serialize import recursive_merge
18
+
19
+ DEFAULT_OUTPUT_FILE = global_config_dir / "last_swebench_single_run.traj.json"
20
+ DEFAULT_CONFIG_FILE = builtin_config_dir / "benchmarks" / "swebench.yaml"
20
21
 
21
22
  app = typer.Typer(add_completion=False)
22
23
 
23
- DEFAULT_OUTPUT = global_config_dir / "last_swebench_single_run.traj.json"
24
+ _CONFIG_SPEC_HELP_TEXT = """Path to config files, filenames, or key-value pairs.
25
+
26
+ [bold red]IMPORTANT:[/bold red] [red]If you set this option, the default config file will not be used.[/red]
27
+ So you need to explicitly set it e.g., with [bold green]-c swebench.yaml <other options>[/bold green]
28
+
29
+ Multiple configs will be recursively merged.
30
+
31
+ Examples:
32
+
33
+ [bold red]-c model.model_kwargs.temperature=0[/bold red] [red]You forgot to add the default config file! See above.[/red]
34
+
35
+ [bold green]-c swebench.yaml -c model.model_kwargs.temperature=0.5[/bold green]
36
+
37
+ [bold green]-c swebench.yaml -c agent.mode=yolo[/bold green]
38
+ """
24
39
 
25
40
 
26
41
  # fmt: off
@@ -31,10 +46,10 @@ def main(
31
46
  instance_spec: str = typer.Option(0, "-i", "--instance", help="SWE-Bench instance ID or index", rich_help_panel="Data selection"),
32
47
  model_name: str | None = typer.Option(None, "-m", "--model", help="Model to use", rich_help_panel="Basic"),
33
48
  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"),
34
- config_path: Path = typer.Option( builtin_config_dir / "extra" / "swebench.yaml", "-c", "--config", help="Path to a config file", rich_help_panel="Basic"),
49
+ config_spec: list[str] = typer.Option([str(DEFAULT_CONFIG_FILE)], "-c", "--config", help=_CONFIG_SPEC_HELP_TEXT, rich_help_panel="Basic"),
35
50
  environment_class: str | None = typer.Option(None, "--environment-class", rich_help_panel="Advanced"),
36
51
  exit_immediately: bool = typer.Option( False, "--exit-immediately", help="Exit immediately when the agent wants to finish instead of prompting.", rich_help_panel="Basic"),
37
- output: Path = typer.Option(DEFAULT_OUTPUT, "-o", "--output", help="Output trajectory file", rich_help_panel="Basic"),
52
+ output: Path = typer.Option(DEFAULT_OUTPUT_FILE, "-o", "--output", help="Output trajectory file", rich_help_panel="Basic"),
38
53
  ) -> None:
39
54
  # fmt: on
40
55
  """Run on a single SWE-Bench instance."""
@@ -48,31 +63,26 @@ def main(
48
63
  instance_spec = sorted(instances.keys())[int(instance_spec)]
49
64
  instance: dict = instances[instance_spec] # type: ignore
50
65
 
51
- config_path = get_config_path(config_path)
52
- logger.info(f"Loading agent config from '{config_path}'")
53
- config = yaml.safe_load(config_path.read_text())
66
+ logger.info(f"Building agent config from specs: {config_spec}")
67
+ configs = [get_config_from_spec(spec) for spec in config_spec]
68
+ configs.append({"agent": {"mode": "yolo"}})
54
69
  if environment_class is not None:
55
- config.setdefault("environment", {})["environment_class"] = environment_class
70
+ configs.append({"environment": {"environment_class": environment_class}})
56
71
  if model_class is not None:
57
- config.setdefault("model", {})["model_class"] = model_class
72
+ configs.append({"model": {"model_class": model_class}})
73
+ if model_name is not None:
74
+ configs.append({"model": {"model_name": model_name}})
58
75
  if exit_immediately:
59
- config.setdefault("agent", {})["confirm_exit"] = False
76
+ configs.append({"agent": {"confirm_exit": False}})
77
+ config = recursive_merge(*configs)
78
+
60
79
  env = get_sb_environment(config, instance)
61
80
  agent = InteractiveAgent(
62
- get_model(model_name, config.get("model", {})),
81
+ get_model(config=config.get("model", {})),
63
82
  env,
64
- **({"mode": "yolo"} | config.get("agent", {})),
83
+ **config.get("agent", {}),
65
84
  )
66
-
67
- exit_status, result, extra_info = None, None, None
68
- try:
69
- exit_status, result = agent.run(instance["problem_statement"]) # type: ignore[arg-type]
70
- except Exception as e:
71
- logger.error(f"Error processing instance {instance_spec}: {e}", exc_info=True)
72
- exit_status, result = type(e).__name__, str(e)
73
- extra_info = {"traceback": traceback.format_exc()}
74
- finally:
75
- save_traj(agent, output, exit_status=exit_status, result=result, extra_info=extra_info) # type: ignore[arg-type]
85
+ agent.run(instance["problem_statement"])
76
86
 
77
87
 
78
88
  if __name__ == "__main__":
@@ -143,8 +143,8 @@ class RunBatchProgressManager:
143
143
  )
144
144
 
145
145
  def on_instance_end(self, instance_id: str, exit_status: str | None) -> None:
146
- self._instances_by_exit_status[exit_status].append(instance_id)
147
146
  with self._lock:
147
+ self._instances_by_exit_status[exit_status].append(instance_id)
148
148
  try:
149
149
  self._task_progress_bar.remove_task(self._spinner_tasks[instance_id])
150
150
  except KeyError:
@@ -1,3 +1,8 @@
1
+ """This is the simplest possible example of how to use mini-SWE-agent with python bindings.
2
+ For a more complete example, see mini.py
3
+ """
4
+
5
+ import logging
1
6
  import os
2
7
  from pathlib import Path
3
8
 
@@ -23,6 +28,7 @@ def main(
23
28
  prompt="What model do you want to use?",
24
29
  ),
25
30
  ) -> DefaultAgent:
31
+ logging.basicConfig(level=logging.DEBUG)
26
32
  agent = DefaultAgent(
27
33
  LitellmModel(model_name=model_name),
28
34
  LocalEnvironment(),
minisweagent/run/mini.py CHANGED
@@ -4,103 +4,94 @@
4
4
  # Read this first: https://mini-swe-agent.com/latest/usage/mini/ (usage)
5
5
 
6
6
  import os
7
- import traceback
8
7
  from pathlib import Path
9
8
  from typing import Any
10
9
 
11
10
  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
11
  from rich.console import Console
17
12
 
18
13
  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
14
+ from minisweagent.agents.interactive import InteractiveAgent, _multiline_prompt
15
+ from minisweagent.config import builtin_config_dir, get_config_from_spec
22
16
  from minisweagent.environments.local import LocalEnvironment
23
17
  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
18
+ from minisweagent.run.utilities.config import configure_if_first_time
19
+ from minisweagent.utils.serialize import UNSET, recursive_merge
27
20
 
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.
21
+ DEFAULT_CONFIG_FILE = Path(os.getenv("MSWEA_MINI_CONFIG_PATH", builtin_config_dir / "mini.yaml"))
22
+ DEFAULT_OUTPUT_FILE = global_config_dir / "last_mini_run.traj.json"
34
23
 
35
- [not dim]
36
- There are two different user interfaces:
37
24
 
38
- [bold green]mini[/bold green] Simple REPL-style interface
39
- [bold green]mini -v[/bold green] Pager-style interface (Textual)
25
+ _HELP_TEXT = """Run mini-SWE-agent in your local environment.
40
26
 
27
+ [not dim]
41
28
  More information about the usage: [bold green]https://mini-swe-agent.com/latest/usage/mini/[/bold green]
42
29
  [/not dim]
43
30
  """
44
31
 
32
+ _CONFIG_SPEC_HELP_TEXT = """Path to config files, filenames, or key-value pairs.
33
+
34
+ [bold red]IMPORTANT:[/bold red] [red]If you set this option, the default config file will not be used.[/red]
35
+ So you need to explicitly set it e.g., with [bold green]-c mini.yaml <other options>[/bold green]
36
+
37
+ Multiple configs will be recursively merged.
38
+
39
+ Examples:
40
+
41
+ [bold red]-c model.model_kwargs.temperature=0[/bold red] [red]You forgot to add the default config file! See above.[/red]
42
+
43
+ [bold green]-c mini.yaml -c model.model_kwargs.temperature=0.5[/bold green]
44
+
45
+ [bold green]-c swebench.yaml agent.mode=yolo[/bold green]
46
+ """
47
+
48
+ console = Console(highlight=False)
49
+ app = typer.Typer(rich_markup_mode="rich")
50
+
45
51
 
46
52
  # fmt: off
47
53
  @app.command(help=_HELP_TEXT)
48
54
  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",),
55
+ model_name: str | None = typer.Option(None, "-m", "--model", help="Model to use",),
51
56
  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
57
  task: str | None = typer.Option(None, "-t", "--task", help="Task/problem statement", show_default=False),
53
58
  yolo: bool = typer.Option(False, "-y", "--yolo", help="Run without confirmation"),
54
59
  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"),
60
+ config_spec: list[str] = typer.Option([str(DEFAULT_CONFIG_FILE)], "-c", "--config", help=_CONFIG_SPEC_HELP_TEXT),
61
+ output: Path | None = typer.Option(DEFAULT_OUTPUT_FILE, "-o", "--output", help="Output trajectory file"),
62
+ 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
63
  ) -> Any:
59
64
  # fmt: on
60
65
  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())
66
+
67
+ # Build the config from the command line arguments
68
+ console.print(f"Building agent config from specs: [bold green]{config_spec}[/bold green]")
69
+ configs = [get_config_from_spec(spec) for spec in config_spec]
70
+ configs.append({
71
+ "agent": {
72
+ "mode": "yolo" if yolo else UNSET,
73
+ "cost_limit": cost_limit or UNSET,
74
+ "confirm_exit": False if exit_immediately else UNSET,
75
+ "output_path": output or UNSET,
76
+ },
77
+ "model": {
78
+ "model_class": model_class or UNSET,
79
+ "model_name": model_name or UNSET,
80
+ },
81
+ })
82
+ config = recursive_merge(*configs)
64
83
 
65
84
  if not task:
66
85
  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
- )
86
+ task = _multiline_prompt()
76
87
  console.print("[bold green]Got that, thanks![/bold green]")
77
88
 
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", {}))
89
+ model = get_model(config=config.get("model", {}))
87
90
  env = LocalEnvironment(**config.get("environment", {}))
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]
91
+ agent = InteractiveAgent(model, env, **config.get("agent", {}))
92
+ agent.run(task) # type: ignore[arg-type]
93
+ if output:
94
+ console.print(f"Saved trajectory to [bold green]'{output}'[/bold green]")
104
95
  return agent
105
96
 
106
97
 
@@ -0,0 +1 @@
1
+ """Utility modules for mini-SWE-agent (config management, inspector, etc.)."""
@@ -1,3 +1,5 @@
1
+ #!/usr/bin/env python3
2
+
1
3
  """Utility to manage the global config file.
2
4
 
3
5
  You can also directly edit the `.env` file in the config directory.
@@ -7,36 +7,92 @@ More information about the usage: [bold green] https://mini-swe-agent.com/latest
7
7
 
8
8
  import json
9
9
  import os
10
+ import subprocess
11
+ import tempfile
10
12
  from pathlib import Path
11
13
 
12
14
  import typer
13
15
  from rich.text import Text
14
16
  from textual.app import App, ComposeResult
15
17
  from textual.binding import Binding
18
+ from textual.command import DiscoveryHit, Hit, Hits, Provider
16
19
  from textual.containers import Container, Vertical, VerticalScroll
17
20
  from textual.widgets import Footer, Header, Static
18
21
 
19
- from minisweagent.agents.interactive_textual import _messages_to_steps
22
+ from minisweagent.models.utils.content_string import get_content_string
23
+
24
+
25
+ def _messages_to_steps(messages: list[dict]) -> list[list[dict]]:
26
+ """Group messages into "pages" as shown by the UI."""
27
+ steps = []
28
+ current_step = []
29
+ for message in messages:
30
+ # Start new step with new tool uses
31
+ if message.get("extra", {}).get("actions") or message.get("role") == "assistant":
32
+ steps.append(current_step)
33
+ current_step = [message]
34
+ else:
35
+ current_step.append(message)
36
+ if current_step:
37
+ steps.append(current_step)
38
+ return steps
39
+
20
40
 
21
41
  app = typer.Typer(rich_markup_mode="rich", add_completion=False)
22
42
 
23
43
 
44
+ class BindingCommandProvider(Provider):
45
+ """Provide bindings as commands in the palette."""
46
+
47
+ COMMAND_DESCRIPTIONS = {
48
+ "next_step": "Next step in the current trajectory",
49
+ "previous_step": "Previous step in the current trajectory",
50
+ "first_step": "First step in the current trajectory",
51
+ "last_step": "Last step in the current trajectory",
52
+ "scroll_down": "Scroll down",
53
+ "scroll_up": "Scroll up",
54
+ "next_trajectory": "Next trajectory",
55
+ "previous_trajectory": "Previous trajectory",
56
+ "open_in_jless": "Open the current step in jless",
57
+ "open_in_jless_all": "Open the entire trajectory in jless",
58
+ "quit": "Quit the inspector",
59
+ }
60
+
61
+ async def discover(self) -> Hits:
62
+ app = self.app
63
+ for binding in app.BINDINGS:
64
+ desc = self.COMMAND_DESCRIPTIONS.get(binding.action, binding.description)
65
+ yield DiscoveryHit(desc, lambda b=binding: app.run_action(b.action))
66
+
67
+ async def search(self, query: str) -> Hits:
68
+ matcher = self.matcher(query)
69
+ app = self.app
70
+ for binding in app.BINDINGS:
71
+ desc = self.COMMAND_DESCRIPTIONS.get(binding.action, binding.description)
72
+ score = matcher.match(desc)
73
+ if score > 0:
74
+ yield Hit(score, matcher.highlight(desc), lambda b=binding: app.run_action(b.action))
75
+
76
+
24
77
  class TrajectoryInspector(App):
78
+ COMMANDS = {BindingCommandProvider}
25
79
  BINDINGS = [
26
80
  Binding("right,l", "next_step", "Step++"),
27
81
  Binding("left,h", "previous_step", "Step--"),
28
82
  Binding("0", "first_step", "Step=0"),
29
83
  Binding("$", "last_step", "Step=-1"),
30
- Binding("j,down", "scroll_down", "Scroll down"),
31
- Binding("k,up", "scroll_up", "Scroll up"),
32
- Binding("L", "next_trajectory", "Next trajectory"),
33
- Binding("H", "previous_trajectory", "Previous trajectory"),
84
+ Binding("j,down", "scroll_down", ""),
85
+ Binding("k,up", "scroll_up", ""),
86
+ Binding("L", "next_trajectory", "Traj++"),
87
+ Binding("H", "previous_trajectory", "Traj--"),
88
+ Binding("e", "open_in_jless", "Jless"),
89
+ Binding("E", "open_in_jless_all", "Jless (all)"),
34
90
  Binding("q", "quit", "Quit"),
35
91
  ]
36
92
 
37
93
  def __init__(self, trajectory_files: list[Path]):
38
94
  css_path = os.environ.get(
39
- "MSWEA_INSPECTOR_STYLE_PATH", str(Path(__file__).parent.parent / "config" / "mini.tcss")
95
+ "MSWEA_INSPECTOR_STYLE_PATH", str(Path(__file__).parent.parent.parent / "config" / "inspector.tcss")
40
96
  )
41
97
  self.__class__.CSS = Path(css_path).read_text()
42
98
 
@@ -142,13 +198,10 @@ class TrajectoryInspector(App):
142
198
  return
143
199
 
144
200
  for message in self.steps[self.i_step]:
145
- if isinstance(message["content"], list):
146
- content_str = "\n".join([item["text"] for item in message["content"]])
147
- else:
148
- content_str = str(message["content"])
201
+ content_str = get_content_string(message)
149
202
  message_container = Vertical(classes="message-container")
150
203
  container.mount(message_container)
151
- role = message["role"].replace("assistant", "mini-swe-agent")
204
+ role = message.get("role") or message.get("type") or "unknown"
152
205
  message_container.mount(Static(role.upper(), classes="message-header"))
153
206
  message_container.mount(Static(Text(content_str, no_wrap=False), classes="message-content"))
154
207
 
@@ -186,6 +239,32 @@ class TrajectoryInspector(App):
186
239
  vs = self.query_one(VerticalScroll)
187
240
  vs.scroll_to(y=vs.scroll_target_y - 15)
188
241
 
242
+ def _open_in_jless(self, path: Path) -> None:
243
+ """Open file in jless."""
244
+ with self.suspend():
245
+ try:
246
+ subprocess.run(["jless", path])
247
+ except FileNotFoundError:
248
+ self.notify("jless not found. Install with: `brew install jless`", severity="error")
249
+
250
+ def action_open_in_jless(self) -> None:
251
+ """Open the current step's messages in jless."""
252
+ if not self.steps:
253
+ self.notify("No messages to display", severity="warning")
254
+ return
255
+ with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
256
+ json.dump(self.steps[self.i_step], f, indent=2)
257
+ temp_path = Path(f.name)
258
+ self._open_in_jless(temp_path)
259
+ temp_path.unlink()
260
+
261
+ def action_open_in_jless_all(self) -> None:
262
+ """Open the entire trajectory in jless."""
263
+ if not self.trajectory_files:
264
+ self.notify("No trajectory to display", severity="warning")
265
+ return
266
+ self._open_in_jless(self.trajectory_files[self.i_trajectory])
267
+
189
268
 
190
269
  @app.command(help=__doc__)
191
270
  def main(
@@ -1,16 +1,20 @@
1
1
  #!/usr/bin/env python3
2
2
 
3
+ """This is the central entry point to the mini-extra script. Use subcommands
4
+ to invoke other command line utilities like running on benchmarks, editing config,
5
+ inspecting trajectories, etc.
6
+ """
7
+
3
8
  import sys
4
9
  from importlib import import_module
5
10
 
6
11
  from rich.console import Console
7
12
 
8
13
  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
+ ("minisweagent.run.utilities.config", ["config"], "Manage the global config file"),
15
+ ("minisweagent.run.utilities.inspector", ["inspect", "i", "inspector"], "Run inspector (browse trajectories)"),
16
+ ("minisweagent.run.benchmarks.swebench", ["swebench"], "Evaluate on SWE-bench (batch mode)"),
17
+ ("minisweagent.run.benchmarks.swebench_single", ["swebench-single"], "Evaluate on SWE-bench (single instance)"),
14
18
  ]
15
19
 
16
20
 
@@ -0,0 +1,26 @@
1
+ from typing import Any
2
+
3
+ UNSET = object()
4
+
5
+
6
+ def recursive_merge(*dictionaries: dict | None) -> dict:
7
+ """Merge multiple dictionaries recursively.
8
+
9
+ Later dictionaries take precedence over earlier ones.
10
+ Nested dictionaries are merged recursively.
11
+ UNSET values are skipped.
12
+ """
13
+ if not dictionaries:
14
+ return {}
15
+ result: dict[str, Any] = {}
16
+ for d in dictionaries:
17
+ if d is None:
18
+ continue
19
+ for key, value in d.items():
20
+ if value is UNSET:
21
+ continue
22
+ if key in result and isinstance(result[key], dict) and isinstance(value, dict):
23
+ result[key] = recursive_merge(result[key], value)
24
+ else:
25
+ result[key] = value
26
+ return result
@@ -1,61 +0,0 @@
1
- mini_swe_agent-1.17.5.dist-info/licenses/LICENSE.md,sha256=D3luWPkdHAe7LBsdD4vzqDAXw6Xewb3G-uczss0uh1s,1094
2
- minisweagent/__init__.py,sha256=mS0JbWq4g29gBFL9_t60714xVzV15INGgXquluOK4RE,2621
3
- minisweagent/__main__.py,sha256=FIyAOiw--c3FQ2g240FOM1FdL0lk_PxSpixu0pQ7WFo,194
4
- minisweagent/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
- minisweagent/agents/__init__.py,sha256=cpjJLzg1IGxLM-tZpoMJV9S33ye13XtdBO0x7DU_Lrk,48
6
- minisweagent/agents/default.py,sha256=No2GmsgUuWzqxUswFKICJNViVDx3GSjecAgK6NP0Tu4,6176
7
- minisweagent/agents/interactive.py,sha256=NBeNamRuqww9ZRhOg1q8xPO9ziUw2gpAVV6hCPbpBxU,7470
8
- minisweagent/agents/interactive_textual.py,sha256=yUDMkuvhhnZAP8LtiBWmt5J5WzfWBeR0zNlJbdbEGa0,18153
9
- minisweagent/config/README.md,sha256=ZGH5KbFHpkwYOwoZgwP1dHOikuvU11dIG_TqyI73dik,355
10
- minisweagent/config/__init__.py,sha256=0KzHaaIqWgRy2zbwIzhrg6BJPDzOvYi3jb4eBNY4sAU,823
11
- minisweagent/config/default.yaml,sha256=z1q91vFq_EeEb2fAuRiIwU7ZabiWP_-29M4GgaBxpgA,5108
12
- minisweagent/config/github_issue.yaml,sha256=Ws1IHO_lGA7JfLzXvDUzpSasf-4hQzXG7EjJqHORtho,4548
13
- minisweagent/config/mini.tcss,sha256=fmAP9cYAp2n7Ps2Dw3e-ZOGEF2E8JcwTgK1LDcis-x4,1141
14
- minisweagent/config/mini.yaml,sha256=eeOO6VkmiQcdU5dddESXUpGKFcSI2gBXsavNni4Xmig,5124
15
- minisweagent/config/extra/__init__.py,sha256=e1MoAlDn_wc9HnXNoncf1P-B4DQ-iRf6n7Q_txjZGRI,52
16
- minisweagent/config/extra/swebench.yaml,sha256=opFzxJPeMYY6oIpB6oUViXiax3ei7UTOlP0Lz1LbFss,7750
17
- minisweagent/config/extra/swebench_roulette.yaml,sha256=ZYkh_ji0e7TuLcWXMrDhUQMPLiYH__EI0C4Skj3sK_Y,7871
18
- minisweagent/config/extra/swebench_xml.yaml,sha256=dWXAqzXgw167hgiKqoqOryPHwAgDV2JbgoiDABdEznY,7827
19
- minisweagent/environments/__init__.py,sha256=x80Ulx0UK21GAwg5jSTkOFeiZ7CQsGBP8cI_5BhazAo,1266
20
- minisweagent/environments/docker.py,sha256=hsKOPGAP2kjgEwA_2HQz_nCrr25qmR4fB8u5Ob6UbzY,4370
21
- minisweagent/environments/local.py,sha256=sOM-8Hc-bmGW6NEMebKz47vFR2Nb0xqvm1Daj6A_mPY,1278
22
- minisweagent/environments/singularity.py,sha256=HSwRTWef7cMCgBiGAh5DIrxW8HkZ9C9ZGtwn0ktD_cw,3675
23
- minisweagent/environments/extra/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
- minisweagent/environments/extra/bubblewrap.py,sha256=G12Dm63N30qByfLb1SKNsI4G4gLyKBfomnOIsPqRNZk,3662
25
- minisweagent/environments/extra/swerex_docker.py,sha256=WPYbohT_vqTHkde9cxpbV6chLXCpLl0PDAcgMbZsV0M,1707
26
- minisweagent/models/__init__.py,sha256=RGJgMPeF8W2Ix8_xwvHjjDCD9I6ygirz4v8Ps1KG6dI,4435
27
- minisweagent/models/anthropic.py,sha256=4p-LxQ_RYQUX1rBsffAj3T1bBb2uMRhA4IyKfDcMpgo,1517
28
- minisweagent/models/litellm_model.py,sha256=0HBnb53GdeqSAwNcJuu8hXrd3XqqIm4BHS1WkwK3ZGE,4394
29
- minisweagent/models/litellm_response_api_model.py,sha256=Niq4HepQm_dlCkGdXsg9_7zNNRxJlnwWK-JJE0lP9Qg,3150
30
- minisweagent/models/openrouter_model.py,sha256=YgML8_p9gHMjoBcxszmgyrIr5DLRX2vZiBwHC47N5mg,4796
31
- minisweagent/models/portkey_model.py,sha256=_eK8maRm2kwombWYbYtH7MXj8rhd18ZE7tGer2YXvws,7307
32
- minisweagent/models/portkey_response_api_model.py,sha256=JcODu40mYsqWnwWntTSjRUMTzifLUZZpFL5sF3FUk3s,2960
33
- minisweagent/models/requesty_model.py,sha256=t4iQD7mH61UlldQmydcGYmoj-dAG8K_2347DBjl-4vo,3865
34
- minisweagent/models/test_models.py,sha256=ItCA6ddntzkYA7dzSuUEaLMV-AE8TBuXBFP8CzpiO3U,1351
35
- minisweagent/models/extra/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
36
- minisweagent/models/extra/roulette.py,sha256=idteU0pGvmmipNr0s-42GAbVkmKE20hY2LTFxbkAgoI,2048
37
- minisweagent/models/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
38
- minisweagent/models/utils/cache_control.py,sha256=zgi9e_72Y1pc0qNirW8_rMyi8RcLonGJNtfavqGX5_I,1917
39
- minisweagent/models/utils/key_per_thread.py,sha256=4YZXATIw-Fozi7M-1i-wyjIBf-GtQM71kkOHxSPkwrE,748
40
- minisweagent/models/utils/openai_utils.py,sha256=3OEOR65gFeVCTpcLJyMkzbFL_B-k8ftmcgvPK1EvqAw,1314
41
- minisweagent/run/__init__.py,sha256=WIoYgHVl7iZF2YncrfV3IttupG6P5KogroKHKECka3A,38
42
- minisweagent/run/github_issue.py,sha256=35mZoPLc4JV6XXJKRv55lnuKbXf5lDftd51N89-x9J0,3192
43
- minisweagent/run/hello_world.py,sha256=erLnEwNmPFLxq3-8zyv66Vy1kIqMqQf97vISX7LrQXg,959
44
- minisweagent/run/inspector.py,sha256=P86kOmzySWdK4tx0DHAOfSF1h-s1vHboSsaRD3_0OKQ,7109
45
- minisweagent/run/mini.py,sha256=08uKeYc_tcAgbi1nbvqmVto-_RhG0NIp5pycvZsk_DM,4930
46
- minisweagent/run/mini_extra.py,sha256=ecA1PnTWElpO60G9RktvVLtUOf3bZ_ESmnSttS6izhQ,1465
47
- minisweagent/run/extra/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
48
- minisweagent/run/extra/config.py,sha256=KDMwg6eQCxbwI6P1phosCwaLQhJQXB4ti65M_HoxU-g,3892
49
- minisweagent/run/extra/swebench.py,sha256=kqLazI4ZOX54ubFE0Md1THOtcdYdW1-7MCBu_z0lza8,11735
50
- minisweagent/run/extra/swebench_single.py,sha256=JHIZkF60_AXfp2LC6CdEqABisEch2XB_udr9bjEO-yM,3693
51
- minisweagent/run/extra/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
52
- minisweagent/run/extra/utils/batch_progress.py,sha256=URgnm5MpUA6liESNFqDIbzELM869PbXro7jKzvdbiv0,6804
53
- minisweagent/run/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
54
- minisweagent/run/utils/save.py,sha256=bokvblZ1SaIvCXimkRQqgvERKmVM0jn8SF7UoHBeerQ,2546
55
- minisweagent/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
56
- minisweagent/utils/log.py,sha256=ruDMNKMrVC9NPvCeHwO3QYz5jsVNUGQB2dRAEAPAWp8,996
57
- mini_swe_agent-1.17.5.dist-info/METADATA,sha256=G47BDX4x97YUXUpe3ccX2-VfbsqFGUZO5xnL6dgyOCM,14836
58
- mini_swe_agent-1.17.5.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
59
- mini_swe_agent-1.17.5.dist-info/entry_points.txt,sha256=d1_yRbTaGjs1UXHa6JQK0sKDGBIVGm8oeW0k2kfbJgQ,182
60
- mini_swe_agent-1.17.5.dist-info/top_level.txt,sha256=zKF4t8bFpV87fdVABZt2Da-vnb4Vkh_CxkwQx5YT4Ew,13
61
- mini_swe_agent-1.17.5.dist-info/RECORD,,
@@ -1,5 +0,0 @@
1
- [console_scripts]
2
- mini = minisweagent.run.mini:app
3
- mini-e = minisweagent.run.mini_extra:main
4
- mini-extra = minisweagent.run.mini_extra:main
5
- mini-swe-agent = minisweagent.run.mini:app