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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions openhands-sdk/openhands/sdk/llm/utils/model_features.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,28 @@ def get_features(model: str) -> ModelFeatures:
)


# Default tools mapping by model pattern. Values are tool names.
DEFAULT_TOOLS: list[tuple[str, list[str]]] = [
# GPT-5 family: prefer apply_patch over file_editor by default
("gpt-5", ["terminal", "apply_patch", "task_tracker", "browser_use"]),
]


def get_default_tools_for_model(model: str | None) -> list[str]:
"""Return the default tool names for a given model.

Falls back to the standard preset tool set when no pattern matches.
"""
if not model:
return ["terminal", "file_editor", "task_tracker", "browser_use"]

for pattern, tools in DEFAULT_TOOLS:
if model_matches(model, [pattern]):
return tools
# Fallback: use standard preset tool set
return ["terminal", "file_editor", "task_tracker", "browser_use"]


# Default temperature mapping.
# Each entry: (pattern, default_temperature)
DEFAULT_TEMPERATURE_PATTERNS: list[tuple[str, float]] = [
Expand Down
35 changes: 27 additions & 8 deletions openhands-tools/openhands/tools/preset/default.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
)
from openhands.sdk.context.condenser.base import CondenserBase
from openhands.sdk.llm.llm import LLM
from openhands.sdk.llm.utils.model_features import get_default_tools_for_model
from openhands.sdk.logger import get_logger
from openhands.sdk.tool import Tool

Expand All @@ -32,28 +33,45 @@ def register_default_tools(enable_browser: bool = True) -> None:

def get_default_tools(
enable_browser: bool = True,
model_name: str | None = None,
) -> list[Tool]:
"""Get the default set of tool specifications for the standard experience.

Args:
enable_browser: Whether to include browser tools.
model_name: Optional raw model name for model-aware defaults.
"""
register_default_tools(enable_browser=enable_browser)

# Import tools to access their name attributes
from openhands.tools.apply_patch import ApplyPatchTool
from openhands.tools.file_editor import FileEditorTool
from openhands.tools.task_tracker import TaskTrackerTool
from openhands.tools.terminal import TerminalTool

tools = [
Tool(name=TerminalTool.name),
Tool(name=FileEditorTool.name),
Tool(name=TaskTrackerTool.name),
]
if enable_browser:
from openhands.tools.browser_use import BrowserToolSet
tool_names = get_default_tools_for_model(model_name)

name_to_cls: dict[str, type] = {
"terminal": TerminalTool,
"file_editor": FileEditorTool,
"task_tracker": TaskTrackerTool,
"apply_patch": ApplyPatchTool,
}

tools: list[Tool] = []
for name in tool_names:
if name == "browser_use" and not enable_browser:
continue
if name == "browser_use" and enable_browser:
from openhands.tools.browser_use import BrowserToolSet

tools.append(Tool(name=BrowserToolSet.name))
continue
cls = name_to_cls.get(name)
if cls is None:
continue
tools.append(Tool(name=cls.name))

tools.append(Tool(name=BrowserToolSet.name))
return tools


Expand All @@ -73,6 +91,7 @@ def get_default_agent(
tools = get_default_tools(
# Disable browser tools in CLI mode
enable_browser=not cli_mode,
model_name=llm.model,
)
agent = Agent(
llm=llm,
Expand Down
41 changes: 41 additions & 0 deletions tests/tools/test_preset_default.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from openhands.sdk.tool import Tool
from openhands.tools.preset.default import get_default_tools


def _tool_names(tools: list[Tool]) -> list[str]:
return [t.name for t in tools]


def test_default_tools_non_gpt5_use_file_editor() -> None:
tools = get_default_tools(enable_browser=True, model_name="openai/gpt-4.1-mini")
names = _tool_names(tools)

assert "file_editor" in names
assert "apply_patch" not in names
assert "terminal" in names
assert "task_tracker" in names
assert "browser_tool_set" in names


def test_default_tools_gpt5_use_apply_patch() -> None:
tools = get_default_tools(
enable_browser=True, model_name="openai/gpt-5.1-codex-mini"
)
names = _tool_names(tools)

assert "apply_patch" in names
assert "file_editor" not in names
assert "terminal" in names
assert "task_tracker" in names
assert "browser_tool_set" in names


def test_default_tools_respect_enable_browser_flag() -> None:
tools = get_default_tools(enable_browser=False, model_name="openai/gpt-5.1-mini")
names = _tool_names(tools)

assert "browser_use" not in names
assert "terminal" in names
assert "task_tracker" in names
# still switching to apply_patch for GPT-5 when browser disabled
assert "apply_patch" in names
Loading