diff --git a/src/serena/agent.py b/src/serena/agent.py index f5c34cfa..29352186 100644 --- a/src/serena/agent.py +++ b/src/serena/agent.py @@ -28,7 +28,7 @@ from serena.constants import ( from serena.dashboard import MemoryLogHandler, SerenaDashboardAPI from serena.project import Project from serena.prompt_factory import SerenaPromptFactory -from serena.tools import Tool, ToolRegistry +from serena.tools import ActivateProjectTool, Tool, ToolRegistry from solidlsp import SolidLanguageServer if TYPE_CHECKING: @@ -177,30 +177,11 @@ class SerenaAgent: # determine the base toolset defining the set of exposed tools (which e.g. the MCP shall see), # limited by the Serena config, the context (which is fixed for the session) and JetBrains mode tool_inclusion_definitions: list[ToolInclusionDefinition] = [self.serena_config, self._context] + if self._context.name == RegisteredContext.IDE_ASSISTANT.value: + tool_inclusion_definitions.extend(self._ide_context_tool_inclusion_definitions(project)) if self.serena_config.jetbrains: tool_inclusion_definitions.append(SerenaAgentMode.from_name_internal("jetbrains")) - # In ide-assistant mode, apply additional tool exclusions - if self._context.name == RegisteredContext.IDE_ASSISTANT.value and project is not None: - additional_excluded_tools = ["activate_project"] - - # If a project is provided, check for excluded tools from project.yml - try: - # Try to load the project to get its excluded tools - project_instance = None - if os.path.isdir(project): - project_instance = Project.load(project, autogenerate=False) - else: - project_instance = self.serena_config.get_project(project) - - if project_instance is not None: - additional_excluded_tools.extend(project_instance.project_config.excluded_tools) - except (FileNotFoundError, ValueError): - # If project.yml doesn't exist or can't be loaded, no additional exclusions - pass - ide_assistant_exclusions = ToolInclusionDefinition(excluded_tools=additional_excluded_tools) - tool_inclusion_definitions.append(ide_assistant_exclusions) - self._base_tool_set = ToolSet.default().apply(*tool_inclusion_definitions) self._exposed_tools = {tc: t for tc, t in self._all_tools.items() if self._base_tool_set.includes_name(t.get_name())} log.info(f"Number of exposed tools: {len(self._exposed_tools)}") @@ -237,6 +218,27 @@ class SerenaAgent: except Exception as e: log.error(f"Error activating project '{project}' at startup: {e}") + def _ide_context_tool_inclusion_definitions(self, project_root_or_name: str | None) -> list[ToolInclusionDefinition]: + """ + In the IDE assistant context, the agent is assumed to work on a single project, and we thus + want to apply that project's tool exclusions/inclusions from the get-go, limiting the set + of tools that will be exposed to the client. + So if the project exists, we apply all the aforementioned exclusions. + + :param project_root_or_name: the project root path or project name + :return: + """ + tool_inclusion_definitions = [] + if project_root_or_name is not None: + # Note: Auto-generation is disabled, because the result must be returned instantaneously + # (project generation could take too much time), so as not to delay MCP server startup + # and provide responses to the client immediately. + project = self.load_project_from_path_or_name(project_root_or_name, autogenerate=False) + if project is not None: + tool_inclusion_definitions.append(ToolInclusionDefinition(excluded_tools=[ActivateProjectTool.get_name_from_cls()])) + tool_inclusion_definitions.append(project.project_config) + return tool_inclusion_definitions + def record_tool_usage_if_enabled(self, input_kwargs: dict, tool_result: str | dict, tool: Tool) -> None: """ Record the usage of a tool with the given input and output strings if tool usage statistics recording is enabled.