mirror of
https://github.com/NCBM/plyngent.git
synced 2026-11-07 15:37:55 +08:00
core/tools: add READ_ONLY tool attribute
New ToolTag.READ_ONLY marks tools that never mutate host state, plus ToolRegistry.clone(read_only_only=True) to select just those (for safe side turns / planning). Builtin read-only set: read_file, get_truncated, glob_paths, grep_files, listdir, tree, todo_list, wait, ask_user_*, vcs_* and fetch. fetch is tagged READ_ONLY but can mutate via method; a new read_only_context ContextVar makes it accept GET only while set.
This commit is contained in:
@@ -4,7 +4,7 @@ from __future__ import annotations
|
||||
|
||||
import inspect
|
||||
|
||||
from plyngent.agent import ToolTag, tool
|
||||
from plyngent.agent import ToolRegistry, ToolTag, tool
|
||||
from plyngent.tools import default_tool_definitions, register_builtin_tools
|
||||
from plyngent.tools.catalog import ToolCatalog, ToolSource, catalog_scope, get_catalog, registration_source
|
||||
from plyngent.tools.chat import CHAT_TOOLS
|
||||
@@ -42,6 +42,45 @@ def test_public_surface_is_subset() -> None:
|
||||
assert "read_file" not in public
|
||||
|
||||
|
||||
def test_read_only_classification() -> None:
|
||||
"""READ_ONLY marks exactly the non-mutating builtins."""
|
||||
register_builtin_tools()
|
||||
read_only = {t.name for t in default_tool_definitions(surface="local") if t.tags & ToolTag.READ_ONLY}
|
||||
assert read_only == {
|
||||
"read_file",
|
||||
"get_truncated",
|
||||
"glob_paths",
|
||||
"grep_files",
|
||||
"listdir",
|
||||
"tree",
|
||||
"todo_list",
|
||||
"wait",
|
||||
"ask_user_line",
|
||||
"ask_user_choice",
|
||||
"ask_user_form",
|
||||
"vcs_kind",
|
||||
"vcs_status",
|
||||
"vcs_diff",
|
||||
"vcs_log",
|
||||
"vcs_branch",
|
||||
"fetch",
|
||||
}
|
||||
|
||||
|
||||
def test_registry_clone_read_only_only() -> None:
|
||||
"""clone(read_only_only=True) keeps only READ_ONLY tools, nothing else."""
|
||||
register_builtin_tools()
|
||||
full = ToolRegistry(default_tool_definitions(surface="local"))
|
||||
ro = full.clone(read_only_only=True)
|
||||
assert 0 < len(ro) < len(full)
|
||||
assert {t.name for t in ro.definitions()} == {t.name for t in full.definitions() if t.tags & ToolTag.READ_ONLY}
|
||||
assert all(t.tags & ToolTag.READ_ONLY for t in ro.definitions())
|
||||
assert "run_argv" not in ro
|
||||
assert "edit_replace" not in ro
|
||||
# Plain clone keeps every tool.
|
||||
assert len(full.clone()) == len(full)
|
||||
|
||||
|
||||
def test_catalog_scope_empty_isolates_registration() -> None:
|
||||
with catalog_scope(empty=True) as catalog:
|
||||
|
||||
|
||||
Reference in New Issue
Block a user