From 9781710f4e634614a872994683e78c9b35877a6e Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Sun, 23 Nov 2025 22:29:53 +0100 Subject: [PATCH] Sync mux commit c5b59a6cb87a093327b3bc2bbfa2a485a3b18d1f Author: Dominik Jain Date: Sun Nov 23 18:53:06 2025 +0100 ToolRegistry: Allow valid tool packages to be modified src-serena/serena/tools/tools_base.py commit 9b4299d4f3e12fe67919e304d9e33c527ee0f345 Author: Dominik Jain Date: Sun Nov 23 17:47:39 2025 +0100 ToolInclusionDefinition: Add option to specify a fixed tool set src-serena/serena/agent.py src-serena/serena/config/serena_config.py --- src/serena/agent.py | 49 ++++++++++++++++++------------ src/serena/config/serena_config.py | 30 ++++++++++++++++-- src/serena/tools/tools_base.py | 5 ++- 3 files changed, 60 insertions(+), 24 deletions(-) diff --git a/src/serena/agent.py b/src/serena/agent.py index dcbe2dbf..70d5bb73 100644 --- a/src/serena/agent.py +++ b/src/serena/agent.py @@ -106,26 +106,35 @@ class ToolSet: registry = ToolRegistry() tool_names = set(self._tool_names) for definition in tool_inclusion_definitions: - included_tools = [] - excluded_tools = [] - for included_tool in definition.included_optional_tools: - included_tool = get_updated_tool_name(included_tool) - if not registry.is_valid_tool_name(included_tool): - raise ValueError(f"Invalid tool name '{included_tool}' provided for inclusion") - if included_tool not in tool_names: - tool_names.add(included_tool) - included_tools.append(included_tool) - for excluded_tool in definition.excluded_tools: - excluded_tool = get_updated_tool_name(excluded_tool) - if not registry.is_valid_tool_name(excluded_tool): - raise ValueError(f"Invalid tool name '{excluded_tool}' provided for exclusion") - if excluded_tool in tool_names: - tool_names.remove(excluded_tool) - excluded_tools.append(excluded_tool) - if included_tools: - log.info(f"{definition} included {len(included_tools)} tools: {', '.join(included_tools)}") - if excluded_tools: - log.info(f"{definition} excluded {len(excluded_tools)} tools: {', '.join(excluded_tools)}") + if definition.is_fixed_tool_set(): + tool_names = set() + for fixed_tool in definition.fixed_tools: + fixed_tool = get_updated_tool_name(fixed_tool) + if not registry.is_valid_tool_name(fixed_tool): + raise ValueError(f"Invalid tool name '{fixed_tool}' provided for fixed tool set") + tool_names.add(fixed_tool) + log.info(f"{definition} defined a fixed tool set with {len(tool_names)} tools: {', '.join(tool_names)}") + else: + included_tools = [] + excluded_tools = [] + for included_tool in definition.included_optional_tools: + included_tool = get_updated_tool_name(included_tool) + if not registry.is_valid_tool_name(included_tool): + raise ValueError(f"Invalid tool name '{included_tool}' provided for inclusion") + if included_tool not in tool_names: + tool_names.add(included_tool) + included_tools.append(included_tool) + for excluded_tool in definition.excluded_tools: + excluded_tool = get_updated_tool_name(excluded_tool) + if not registry.is_valid_tool_name(excluded_tool): + raise ValueError(f"Invalid tool name '{excluded_tool}' provided for exclusion") + if excluded_tool in tool_names: + tool_names.remove(excluded_tool) + excluded_tools.append(excluded_tool) + if included_tools: + log.info(f"{definition} included {len(included_tools)} tools: {', '.join(included_tools)}") + if excluded_tools: + log.info(f"{definition} excluded {len(excluded_tools)} tools: {', '.join(excluded_tools)}") return ToolSet(tool_names) def without_editing_tools(self) -> "ToolSet": diff --git a/src/serena/config/serena_config.py b/src/serena/config/serena_config.py index c5345d48..6cc74f84 100644 --- a/src/serena/config/serena_config.py +++ b/src/serena/config/serena_config.py @@ -5,7 +5,7 @@ The Serena Model Context Protocol (MCP) Server import dataclasses import os import shutil -from collections.abc import Iterable +from collections.abc import Sequence from copy import deepcopy from dataclasses import dataclass, field from datetime import datetime @@ -72,8 +72,32 @@ class SerenaPaths: @dataclass class ToolInclusionDefinition: - excluded_tools: Iterable[str] = () - included_optional_tools: Iterable[str] = () + """ + Defines which tools to include/exclude in Serena's operation. + This can mean either + * defining exclusions/inclusions to apply to an existing set of tools [incremental mode], or + * defining a fixed set of tools to use [fixed mode]. + """ + + excluded_tools: Sequence[str] = () + """ + the names of tools to exclude from use [incremental mode] + """ + included_optional_tools: Sequence[str] = () + """ + the names of optional tools to include [incremental mode] + """ + fixed_tools: Sequence[str] = () + """ + the names of tools to use as a fixed set of tools [fixed mode] + """ + + def is_fixed_tool_set(self) -> bool: + num_fixed = len(self.fixed_tools) + num_incremental = len(self.excluded_tools) + len(self.included_optional_tools) + if num_fixed > 0 and num_incremental > 0: + raise ValueError("Cannot use both fixed_tools and excluded_tools/included_optional_tools at the same time.") + return num_fixed > 0 class SerenaConfigError(Exception): diff --git a/src/serena/tools/tools_base.py b/src/serena/tools/tools_base.py index a79f2911..cb163357 100644 --- a/src/serena/tools/tools_base.py +++ b/src/serena/tools/tools_base.py @@ -350,12 +350,15 @@ class RegisteredTool: tool_name: str +tool_packages = ["serena.tools"] + + @singleton class ToolRegistry: def __init__(self) -> None: self._tool_dict: dict[str, RegisteredTool] = {} for cls in iter_subclasses(Tool): - if not cls.__module__.startswith("serena.tools"): + if not any(cls.__module__.startswith(pkg) for pkg in tool_packages): continue is_optional = issubclass(cls, ToolMarkerOptional) name = cls.get_name_from_cls()