mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-08-06 01:18:45 +00:00
fix(tools): hide exec compatibility aliases from schema
This commit is contained in:
parent
c78421cf16
commit
9d6ace16ef
@ -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,51 +74,55 @@ 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"),
|
working_dir=StringSchema("Optional working directory for the command"),
|
||||||
cmd=StringSchema("Compatibility alias for command"),
|
timeout=IntegerSchema(
|
||||||
working_dir=StringSchema("Optional working directory for the command"),
|
60,
|
||||||
workdir=StringSchema("Compatibility alias for working_dir"),
|
description=(
|
||||||
timeout=IntegerSchema(
|
"Timeout in seconds. Increase for long-running commands "
|
||||||
60,
|
"like compilation or installation (default 60, max 600)."
|
||||||
description=(
|
|
||||||
"Timeout in seconds. Increase for long-running commands "
|
|
||||||
"like compilation or installation (default 60, max 600)."
|
|
||||||
),
|
|
||||||
minimum=1,
|
|
||||||
maximum=600,
|
|
||||||
),
|
),
|
||||||
shell=StringSchema(
|
minimum=1,
|
||||||
"Optional shell binary to launch. On Unix, supports sh, bash, or zsh.",
|
maximum=600,
|
||||||
nullable=True,
|
),
|
||||||
|
shell=StringSchema(
|
||||||
|
"Optional shell binary to launch. On Unix, supports sh, bash, or zsh.",
|
||||||
|
nullable=True,
|
||||||
|
),
|
||||||
|
login=BooleanSchema(
|
||||||
|
description="Whether to run bash/zsh with login shell semantics (default false).",
|
||||||
|
default=False,
|
||||||
|
nullable=True,
|
||||||
|
),
|
||||||
|
yield_time_ms=IntegerSchema(
|
||||||
|
description=(
|
||||||
|
"Optional milliseconds to wait before returning output. "
|
||||||
|
"When set, a still-running command returns a session_id that "
|
||||||
|
"can be polled or written to with write_stdin. Omit this field "
|
||||||
|
"to keep one-shot exec behavior."
|
||||||
),
|
),
|
||||||
login=BooleanSchema(
|
minimum=0,
|
||||||
description="Whether to run bash/zsh with login shell semantics (default false).",
|
maximum=MAX_YIELD_MS,
|
||||||
default=False,
|
nullable=True,
|
||||||
nullable=True,
|
),
|
||||||
|
max_output_chars=IntegerSchema(
|
||||||
|
description=(
|
||||||
|
"Maximum output characters to return when yield_time_ms is used "
|
||||||
|
"(default 10000, max 50000)."
|
||||||
),
|
),
|
||||||
yield_time_ms=IntegerSchema(
|
minimum=1000,
|
||||||
description=(
|
maximum=MAX_OUTPUT_CHARS,
|
||||||
"Optional milliseconds to wait before returning output. "
|
nullable=True,
|
||||||
"When set, a still-running command returns a session_id that "
|
),
|
||||||
"can be polled or written to with write_stdin. Omit this field "
|
)
|
||||||
"to keep one-shot exec behavior."
|
|
||||||
),
|
_EXEC_TOOL_COMPAT_PARAMETERS = deepcopy(_EXEC_TOOL_PARAMETERS)
|
||||||
minimum=0,
|
_EXEC_TOOL_COMPAT_PARAMETERS["properties"].update(
|
||||||
maximum=MAX_YIELD_MS,
|
{
|
||||||
nullable=True,
|
"cmd": StringSchema("Compatibility alias for command").to_json_schema(),
|
||||||
),
|
"workdir": StringSchema("Compatibility alias for working_dir").to_json_schema(),
|
||||||
max_output_chars=IntegerSchema(
|
"max_output_tokens": IntegerSchema(
|
||||||
description=(
|
|
||||||
"Maximum output characters to return when yield_time_ms is used "
|
|
||||||
"(default 10000, max 50000)."
|
|
||||||
),
|
|
||||||
minimum=1000,
|
|
||||||
maximum=MAX_OUTPUT_CHARS,
|
|
||||||
nullable=True,
|
|
||||||
),
|
|
||||||
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,
|
||||||
|
|||||||
@ -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()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user