fix(tools): hide exec compatibility aliases from schema

This commit is contained in:
chengyongru 2026-07-02 11:54:36 +08:00
parent c78421cf16
commit 9d6ace16ef
2 changed files with 98 additions and 46 deletions

View File

@ -8,6 +8,7 @@ import re
import shutil import shutil
import sys import sys
from contextlib import suppress from contextlib import suppress
from copy import deepcopy
from dataclasses import dataclass from dataclasses import dataclass
from pathlib import Path from pathlib import Path
from typing import Any from typing import Any
@ -15,7 +16,7 @@ from typing import Any
from loguru import logger from loguru import logger
from pydantic import Field from pydantic import Field
from nanobot.agent.tools.base import Tool, ToolResult, tool_parameters from nanobot.agent.tools.base import Schema, Tool, ToolResult, tool_parameters
from nanobot.agent.tools.context import current_request_session_key from nanobot.agent.tools.context import current_request_session_key
from nanobot.agent.tools.exec_session import ( from nanobot.agent.tools.exec_session import (
DEFAULT_EXEC_SESSION_MANAGER, DEFAULT_EXEC_SESSION_MANAGER,
@ -73,12 +74,9 @@ class _PreparedCommand:
login: bool login: bool
@tool_parameters( _EXEC_TOOL_PARAMETERS = tool_parameters_schema(
tool_parameters_schema(
command=StringSchema("The shell command to execute"), command=StringSchema("The shell command to execute"),
cmd=StringSchema("Compatibility alias for command"),
working_dir=StringSchema("Optional working directory for the command"), working_dir=StringSchema("Optional working directory for the command"),
workdir=StringSchema("Compatibility alias for working_dir"),
timeout=IntegerSchema( timeout=IntegerSchema(
60, 60,
description=( description=(
@ -117,7 +115,14 @@ class _PreparedCommand:
maximum=MAX_OUTPUT_CHARS, maximum=MAX_OUTPUT_CHARS,
nullable=True, nullable=True,
), ),
max_output_tokens=IntegerSchema( )
_EXEC_TOOL_COMPAT_PARAMETERS = deepcopy(_EXEC_TOOL_PARAMETERS)
_EXEC_TOOL_COMPAT_PARAMETERS["properties"].update(
{
"cmd": StringSchema("Compatibility alias for command").to_json_schema(),
"workdir": StringSchema("Compatibility alias for working_dir").to_json_schema(),
"max_output_tokens": IntegerSchema(
description=( description=(
"Compatibility alias for max_output_chars. The current runtime " "Compatibility alias for max_output_chars. The current runtime "
"uses a character budget." "uses a character budget."
@ -125,9 +130,12 @@ class _PreparedCommand:
minimum=1000, minimum=1000,
maximum=MAX_OUTPUT_CHARS, maximum=MAX_OUTPUT_CHARS,
nullable=True, nullable=True,
), ).to_json_schema(),
) }
) )
@tool_parameters(_EXEC_TOOL_PARAMETERS)
class ExecTool(Tool): class ExecTool(Tool):
"""Tool to execute shell commands.""" """Tool to execute shell commands."""
_scopes = {"core", "subagent"} _scopes = {"core", "subagent"}
@ -244,6 +252,18 @@ class ExecTool(Tool):
def exclusive(self) -> bool: def exclusive(self) -> bool:
return True return True
def cast_params(self, params: dict[str, Any]) -> dict[str, Any]:
return self._cast_object(params, _EXEC_TOOL_COMPAT_PARAMETERS)
def validate_params(self, params: dict[str, Any]) -> list[str]:
if not isinstance(params, dict):
return [f"parameters must be an object, got {type(params).__name__}"]
return Schema.validate_json_schema_value(
params,
{**_EXEC_TOOL_COMPAT_PARAMETERS, "type": "object"},
"",
)
async def execute( async def execute(
self, command: str | None = None, cmd: str | None = None, self, command: str | None = None, cmd: str | None = None,
working_dir: str | None = None, workdir: str | None = None, working_dir: str | None = None, workdir: str | None = None,

View File

@ -11,7 +11,7 @@ from nanobot.agent.tools.exec_session import (
ListExecSessionsTool, ListExecSessionsTool,
WriteStdinTool, WriteStdinTool,
) )
from nanobot.agent.tools.registry import is_tool_error_result from nanobot.agent.tools.registry import ToolRegistry, is_tool_error_result
from nanobot.agent.tools.shell import ExecTool from nanobot.agent.tools.shell import ExecTool
@ -53,6 +53,38 @@ def test_exec_accepts_command_aliases(tmp_path):
assert "Exit code: 0" in result assert "Exit code: 0" in result
def test_exec_schema_hides_compatibility_aliases():
props = ExecTool().parameters["properties"]
assert "command" in props
assert "working_dir" in props
assert "max_output_chars" in props
assert "cmd" not in props
assert "workdir" not in props
assert "max_output_tokens" not in props
def test_exec_registry_accepts_hidden_compatibility_aliases(tmp_path):
async def run() -> str:
registry = ToolRegistry()
registry.register(ExecTool(working_dir="/", timeout=5))
command = _python_command("import os; print(os.getcwd()); print('A' * 2000)")
return await registry.execute(
"exec",
{
"cmd": command,
"workdir": str(tmp_path),
"max_output_tokens": 1000,
},
)
result = asyncio.run(run())
assert str(tmp_path) in result
assert "chars truncated" in result
assert "Exit code: 0" in result
def test_exec_returns_completed_session_output_when_yield_time_ms_is_used(tmp_path): def test_exec_returns_completed_session_output_when_yield_time_ms_is_used(tmp_path):
async def run() -> str: async def run() -> str:
manager = ExecSessionManager() manager = ExecSessionManager()