Skip to content

Commit 16c798b

Browse files
fix: add git and reset tool name aliases for trinity-large-thinking
Add 'git' and 'reset' tool name aliases that map to 'terminal' to fix errors like 'Tool 'git' not found' and 'Tool 'reset' not found' seen in trinity-large-thinking evaluation. For these aliases, the tool name is prepended to the command argument (e.g., 'git status' from tool_name='git' + command='status'). Unlike 'bash' which passes through the command directly, these tools are themselves commands that should be combined with their arguments. Also adds 'git' to _SHELL_TOOL_FALLBACK_COMMANDS so 'git' without arguments still falls back to terminal. Co-authored-by: openhands <openhands@all-hands.dev>
1 parent 74112a9 commit 16c798b

File tree

2 files changed

+80
-1
lines changed

2 files changed

+80
-1
lines changed

openhands-sdk/openhands/sdk/agent/utils.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,13 +175,21 @@ def fix_malformed_tool_arguments(
175175
"command": "terminal",
176176
"execute": "terminal",
177177
"execute_bash": "terminal",
178+
"git": "terminal",
179+
"reset": "terminal",
178180
"str_replace": "file_editor",
179181
"str_replace_editor": "file_editor",
180182
}
181183

184+
# Terminal aliases that prepend the tool name to the command argument.
185+
# Unlike 'bash' which passes through the command directly, these tools
186+
# (e.g., 'git', 'reset') are themselves commands that should be combined
187+
# with their arguments (e.g., 'git status', 'reset clear').
188+
_TERMINAL_COMMAND_PREFIX_ALIASES = frozenset({"git", "reset"})
189+
182190
# This fallback is intentionally tiny: it only accepts exact, bare command names
183191
# that are useful as read-only defaults when some models emit them as tool names.
184-
_SHELL_TOOL_FALLBACK_COMMANDS = frozenset({"find", "ls", "pwd"})
192+
_SHELL_TOOL_FALLBACK_COMMANDS = frozenset({"find", "git", "ls", "pwd"})
185193

186194
# Typo normalization for common mistakes in security_risk field
187195
_SECURITY_RISK_TYPOS = {"security_rort", "securtiy_risk", "security_riks"}
@@ -306,6 +314,21 @@ def normalize_tool_call(
306314
alias_target = TOOL_NAME_ALIASES.get(tool_name)
307315
if alias_target and alias_target in available_tools:
308316
normalized_tool_name = alias_target
317+
# For terminal alias with prefix, combine tool name with command
318+
if (
319+
alias_target == "terminal"
320+
and tool_name in _TERMINAL_COMMAND_PREFIX_ALIASES
321+
):
322+
original_command = arguments.get("command")
323+
normalized_arguments = {
324+
key: value
325+
for key, value in arguments.items()
326+
if key in {"security_risk", "summary"}
327+
}
328+
if original_command:
329+
normalized_arguments["command"] = f"{tool_name} {original_command}"
330+
else:
331+
normalized_arguments["command"] = tool_name
309332
elif "terminal" in available_tools:
310333
terminal_command = _maybe_rewrite_as_terminal_command(
311334
tool_name,

tests/sdk/agent/test_tool_call_compatibility.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -470,3 +470,59 @@ def test_explicitly_registered_tool_not_hijacked_by_alias():
470470
"str_replace", {"old_str": "x", "new_str": "y"}, available_tools
471471
)
472472
assert tool_name == "file_editor", "str_replace alias should map to file_editor"
473+
474+
475+
def test_git_alias_executes_terminal_tool(tmp_path):
476+
"""Test that 'git' tool name is aliased to 'terminal'."""
477+
events = _run_tool_call(
478+
tmp_path,
479+
tool_name="git",
480+
arguments={"command": "status"},
481+
tool_names=(TERMINAL_TOOL_SPEC,),
482+
)
483+
484+
action_event = next(e for e in events if isinstance(e, ActionEvent))
485+
errors = [e for e in events if isinstance(e, AgentErrorEvent)]
486+
487+
assert not errors
488+
assert action_event.tool_name == TERMINAL_TOOL_NAME
489+
assert action_event.tool_call.name == TERMINAL_TOOL_NAME
490+
assert action_event.action is not None
491+
assert getattr(action_event.action, "command") == "git status"
492+
493+
494+
def test_reset_alias_executes_terminal_tool(tmp_path):
495+
"""Test that 'reset' tool name is aliased to 'terminal'."""
496+
events = _run_tool_call(
497+
tmp_path,
498+
tool_name="reset",
499+
arguments={"command": "clear"},
500+
tool_names=(TERMINAL_TOOL_SPEC,),
501+
)
502+
503+
action_event = next(e for e in events if isinstance(e, ActionEvent))
504+
errors = [e for e in events if isinstance(e, AgentErrorEvent)]
505+
506+
assert not errors
507+
assert action_event.tool_name == TERMINAL_TOOL_NAME
508+
assert action_event.tool_call.name == TERMINAL_TOOL_NAME
509+
assert action_event.action is not None
510+
assert getattr(action_event.action, "command") == "reset clear"
511+
512+
513+
def test_shell_tool_name_git_falls_back_to_terminal(tmp_path):
514+
"""Test that 'git' without arguments falls back to terminal."""
515+
events = _run_tool_call(
516+
tmp_path,
517+
tool_name="git",
518+
arguments={},
519+
tool_names=(TERMINAL_TOOL_SPEC,),
520+
)
521+
522+
action_event = next(e for e in events if isinstance(e, ActionEvent))
523+
errors = [e for e in events if isinstance(e, AgentErrorEvent)]
524+
525+
assert not errors
526+
assert action_event.tool_name == TERMINAL_TOOL_NAME
527+
assert action_event.action is not None
528+
assert getattr(action_event.action, "command") == "git"

0 commit comments

Comments
 (0)