mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-14 04:16:34 +00:00
Merge pull request #1074 from oraios/fix-agent-init
Improvements in SerenaAgent.__init__
This commit is contained in:
1 file changed
+52
-63
+52
-63
@@ -17,7 +17,13 @@ from interprompt.jinja_template import JinjaTemplate
|
||||
from serena import serena_version
|
||||
from serena.analytics import RegisteredTokenCountEstimator, ToolUsageStats
|
||||
from serena.config.context_mode import SerenaAgentContext, SerenaAgentMode
|
||||
from serena.config.serena_config import LanguageBackend, ModeSelectionDefinition, ProjectConfig, SerenaConfig, ToolInclusionDefinition
|
||||
from serena.config.serena_config import (
|
||||
LanguageBackend,
|
||||
ModeSelectionDefinition,
|
||||
RegisteredProject,
|
||||
SerenaConfig,
|
||||
ToolInclusionDefinition,
|
||||
)
|
||||
from serena.dashboard import SerenaDashboardAPI
|
||||
from serena.ls_manager import LanguageServerManager
|
||||
from serena.project import Project
|
||||
@@ -240,6 +246,11 @@ class SerenaAgent:
|
||||
# project-specific instances, which will be initialized upon project activation
|
||||
self._active_project: Project | None = None
|
||||
|
||||
# determine registered project to be activated (if any)
|
||||
registered_project_to_activate: RegisteredProject | None = (
|
||||
self.serena_config.get_registered_project(project, autoregister=True) if project is not None else None
|
||||
)
|
||||
|
||||
# dashboard URL (set when dashboard is started)
|
||||
self._dashboard_url: str | None = None
|
||||
|
||||
@@ -300,6 +311,16 @@ class SerenaAgent:
|
||||
|
||||
self._check_shell_settings()
|
||||
|
||||
# determine the effective language backend for this session.
|
||||
# If a startup project is provided and has a per-project override, use it; otherwise use the global config.
|
||||
# Since we don't want to change the toolset after startup, the language backend cannot be changed within a running Serena session
|
||||
self._language_backend = self.serena_config.language_backend
|
||||
if registered_project_to_activate is not None and registered_project_to_activate.project_config.language_backend is not None:
|
||||
self._language_backend = registered_project_to_activate.project_config.language_backend
|
||||
log.info(f"Using language backend as configured in project.yml: {self._language_backend.name}")
|
||||
else:
|
||||
log.info(f"Using language backend from global configuration: {self._language_backend.name}")
|
||||
|
||||
# determine the base toolset defining the set of exposed tools (which e.g. the MCP shall see),
|
||||
# determined by the
|
||||
# * dashboard availability/opening on launch
|
||||
@@ -317,23 +338,7 @@ class SerenaAgent:
|
||||
tool_inclusion_definitions.append(self.serena_config)
|
||||
tool_inclusion_definitions.append(self._context)
|
||||
if self._context.single_project:
|
||||
tool_inclusion_definitions.extend(self._single_project_context_tool_inclusion_definitions(project))
|
||||
|
||||
# Determine the effective language backend for this session.
|
||||
# If a startup project is provided and has a per-project override, use it; otherwise use the global config.
|
||||
# Since we don't want to change the toolset after startup, the language backend cannot be changed within a running serena session
|
||||
self._language_backend = self.serena_config.language_backend
|
||||
_backend_from_project_config = False
|
||||
if project is not None:
|
||||
startup_project_config = self._find_project_config(project)
|
||||
if startup_project_config is not None and startup_project_config.language_backend is not None:
|
||||
self._language_backend = startup_project_config.language_backend
|
||||
_backend_from_project_config = True
|
||||
if _backend_from_project_config:
|
||||
log.info(f"Using language backend as configured in project.yml: {self._language_backend.name}")
|
||||
else:
|
||||
log.info(f"Using language backend from global serena config: {self._language_backend.name}")
|
||||
|
||||
tool_inclusion_definitions.extend(self._single_project_context_tool_inclusion_definitions(registered_project_to_activate))
|
||||
if self._language_backend == LanguageBackend.JETBRAINS:
|
||||
tool_inclusion_definitions.append(SerenaAgentMode.from_name_internal("jetbrains"))
|
||||
self._base_tool_set = ToolSet.default().apply(*tool_inclusion_definitions)
|
||||
@@ -432,7 +437,9 @@ class SerenaAgent:
|
||||
os.environ["COMSPEC"] = "" # force use of default shell
|
||||
log.info("Adjusting COMSPEC environment variable to use the default shell instead of '%s'", comspec)
|
||||
|
||||
def _single_project_context_tool_inclusion_definitions(self, project_root_or_name: str | None) -> list[ToolInclusionDefinition]:
|
||||
def _single_project_context_tool_inclusion_definitions(
|
||||
self, registered_project: RegisteredProject | None
|
||||
) -> list[ToolInclusionDefinition]:
|
||||
"""
|
||||
When in a single-project 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
|
||||
@@ -440,25 +447,24 @@ class SerenaAgent:
|
||||
Furthermore, we disable tools that are only relevant for project activation.
|
||||
So if the project exists, we apply all the aforementioned exclusions.
|
||||
|
||||
:param project_root_or_name: the project root path or project name
|
||||
:return:
|
||||
NOTE: This uses an existing registered project (instead of project auto-generation),
|
||||
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.
|
||||
|
||||
:param registered_project: the registered project or None
|
||||
:return: the list of tool inclusion definitions to apply for a single-project context, based on the given registered project (if any)
|
||||
"""
|
||||
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:
|
||||
log.info(
|
||||
"Applying tool inclusion/exclusion definitions for single-project context based on project '%s'", project.project_name
|
||||
)
|
||||
tool_inclusion_definitions.append(
|
||||
ToolInclusionDefinition(
|
||||
excluded_tools=[ActivateProjectTool.get_name_from_cls(), GetCurrentConfigTool.get_name_from_cls()]
|
||||
)
|
||||
)
|
||||
tool_inclusion_definitions.append(project.project_config)
|
||||
if registered_project is not None:
|
||||
log.info(
|
||||
"Applying tool inclusion/exclusion definitions for single-project context based on project '%s'",
|
||||
registered_project.project_name,
|
||||
)
|
||||
tool_inclusion_definitions.append(
|
||||
ToolInclusionDefinition(excluded_tools=[ActivateProjectTool.get_name_from_cls(), GetCurrentConfigTool.get_name_from_cls()])
|
||||
)
|
||||
tool_inclusion_definitions.append(registered_project.project_config)
|
||||
return tool_inclusion_definitions
|
||||
|
||||
def record_tool_usage(self, input_kwargs: dict, tool_result: str | dict, tool: Tool) -> None:
|
||||
@@ -636,13 +642,6 @@ class SerenaAgent:
|
||||
"""
|
||||
return self._language_backend == LanguageBackend.LSP
|
||||
|
||||
def _find_project_config(self, project_path_or_name: str) -> ProjectConfig | None:
|
||||
"""Find the ProjectConfig for the given project path or name, or None if not found."""
|
||||
registered = self.serena_config.get_registered_project(project_path_or_name)
|
||||
if registered is not None:
|
||||
return registered.project_config
|
||||
return None
|
||||
|
||||
def _activate_project(self, project: Project, update_modes_and_tools: bool = True) -> None:
|
||||
log.info(f"Activating {project.project_name} at {project.project_root}")
|
||||
|
||||
@@ -673,23 +672,6 @@ class SerenaAgent:
|
||||
if self._project_activation_callback is not None:
|
||||
self._project_activation_callback()
|
||||
|
||||
def load_project_from_path_or_name(self, project_root_or_name: str, autogenerate: bool) -> Project | None:
|
||||
"""
|
||||
Get a project instance from a path or a name.
|
||||
|
||||
:param project_root_or_name: the path to the project root or the name of the project
|
||||
:param autogenerate: whether to autogenerate the project for the case where first argument is a directory
|
||||
which does not yet contain a Serena project configuration file
|
||||
:return: the project instance if it was found/could be created, None otherwise
|
||||
"""
|
||||
project_instance: Project | None = self.serena_config.get_project(project_root_or_name)
|
||||
if project_instance is not None:
|
||||
log.info(f"Found registered project '{project_instance.project_name}' at path {project_instance.project_root}")
|
||||
elif autogenerate and os.path.isdir(project_root_or_name):
|
||||
project_instance = self.serena_config.add_project_from_path(project_root_or_name)
|
||||
log.info(f"Added new project {project_instance.project_name} for path {project_instance.project_root}")
|
||||
return project_instance
|
||||
|
||||
def activate_project_from_path_or_name(self, project_root_or_name: str, update_modes_and_tools: bool = True) -> Project:
|
||||
"""
|
||||
Activate a project from a path or a name.
|
||||
@@ -697,16 +679,23 @@ class SerenaAgent:
|
||||
If the argument is a path at which no Serena project previously existed, the project will be created beforehand.
|
||||
Raises ProjectNotFoundError if the project could neither be found nor created.
|
||||
|
||||
:return: a tuple of the project instance and a Boolean indicating whether the project was newly
|
||||
created
|
||||
:return: the project instance
|
||||
"""
|
||||
project_instance: Project | None = self.load_project_from_path_or_name(project_root_or_name, autogenerate=True)
|
||||
project_instance: Project | None = self.serena_config.get_project(project_root_or_name)
|
||||
if project_instance is not None:
|
||||
log.info(f"Found registered project '{project_instance.project_name}' at path {project_instance.project_root}")
|
||||
elif os.path.isdir(project_root_or_name):
|
||||
project_instance = self.serena_config.add_project_from_path(project_root_or_name)
|
||||
log.info(f"Added new project {project_instance.project_name} for path {project_instance.project_root}")
|
||||
|
||||
if project_instance is None:
|
||||
raise ProjectNotFoundError(
|
||||
f"Project '{project_root_or_name}' not found: Not a valid project name or directory. "
|
||||
f"Existing project names: {self.serena_config.project_names}"
|
||||
)
|
||||
|
||||
self._activate_project(project_instance, update_modes_and_tools=update_modes_and_tools)
|
||||
|
||||
return project_instance
|
||||
|
||||
def get_active_tool_names(self) -> list[str]:
|
||||
|
||||
Reference in new issue
Block a user