mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-14 10:19:02 +00:00
Add agent_interface setting selecting the tool or REPL interface
Introduce AgentInterface (tools/REPL) as a setting in the global configuration, overridable per project (resolution: project > global > default "tools") and via the CLI option --agent-interface. The interface is fixed for the session, like the language backend. In REPL mode, the set of exposed tools is fixed: the REPL tool, initial_instructions and (unless in a single-project session) activate_project. Tool inclusion/exclusion definitions do not apply in REPL mode, as they pertain to the tool interface; the REPL is configured via API inclusions/exclusions. The REPL tool is optional in tool mode. SerenaAgent.is_single_project records whether the session is a single-project session.
This commit is contained in:
1 parent
e7aa6a74e1
commit
337d545c4b
10 files changed
+244
-20
No files matched your search
@@ -10,6 +10,7 @@ import pytest
|
||||
from serena.agent import SerenaAgent
|
||||
from serena.config.serena_config import (
|
||||
DEFAULT_PROJECT_SERENA_FOLDER_LOCATION,
|
||||
AgentInterface,
|
||||
LanguageBackend,
|
||||
ProjectConfig,
|
||||
RegisteredProject,
|
||||
@@ -218,6 +219,65 @@ class TestProjectConfigLanguageBackend:
|
||||
assert config.language_backend is None
|
||||
|
||||
|
||||
class TestAgentInterface:
|
||||
"""Tests for the agent_interface setting (global and per project)."""
|
||||
|
||||
@staticmethod
|
||||
def _project_config(agent_interface: AgentInterface | None) -> ProjectConfig:
|
||||
return ProjectConfig(project_name="test", language_servers=[LanguageServerId.PYTHON], agent_interface=agent_interface)
|
||||
|
||||
def test_agent_interface_roundtrips_through_project_yaml(self):
|
||||
assert self._project_config(AgentInterface.REPL)._to_yaml_dict()["agent_interface"] == "REPL"
|
||||
assert self._project_config(None)._to_yaml_dict()["agent_interface"] is None
|
||||
|
||||
def test_agent_interface_parsed_from_project_dict(self):
|
||||
data, _ = ProjectConfig._load_yaml_dict(PROJECT_TEMPLATE_FILE)
|
||||
data["project_name"] = "test"
|
||||
data["languages"] = ["python"]
|
||||
data["agent_interface"] = "repl" # case-insensitive
|
||||
assert ProjectConfig._from_dict(data, local_override_keys=[]).agent_interface == AgentInterface.REPL
|
||||
data.pop("agent_interface")
|
||||
assert ProjectConfig._from_dict(data, local_override_keys=[]).agent_interface is None
|
||||
|
||||
def test_determine_agent_interface_precedence(self):
|
||||
# default
|
||||
assert SerenaConfig().determine_agent_interface() == AgentInterface.TOOLS
|
||||
assert SerenaConfig().determine_agent_interface(self._project_config(None)) == AgentInterface.TOOLS
|
||||
# global configuration
|
||||
assert SerenaConfig(agent_interface=AgentInterface.REPL).determine_agent_interface() == AgentInterface.REPL
|
||||
# project configuration takes precedence
|
||||
config = SerenaConfig(agent_interface=AgentInterface.REPL)
|
||||
assert config.determine_agent_interface(self._project_config(AgentInterface.TOOLS)) == AgentInterface.TOOLS
|
||||
assert config.determine_agent_interface(self._project_config(None)) == AgentInterface.REPL
|
||||
|
||||
def test_repl_toolset_is_fixed_and_repl_follows_project_activation(self):
|
||||
"""
|
||||
In REPL mode, neither the exposed nor the active toolset is affected by tool inclusion/exclusion definitions
|
||||
(here: the project's exclusions and read-only setting), whereas the REPL's API scope follows the active project.
|
||||
"""
|
||||
config, name = _make_config_with_project("test_proj")
|
||||
config.agent_interface = AgentInterface.REPL
|
||||
project_config = config.projects[0].project_config
|
||||
project_config.excluded_tools = ["initial_instructions", "serena_repl"]
|
||||
project_config.excluded_apis = ["mem"]
|
||||
project_config.read_only = True
|
||||
|
||||
agent = SerenaAgent(project=None, serena_config=config)
|
||||
try:
|
||||
# before activation: the fixed toolset and the full set of facades
|
||||
fixed_toolset = {"serena_repl", "initial_instructions", "activate_project"}
|
||||
assert {t.get_name() for t in agent.get_exposed_tool_instances()} == fixed_toolset
|
||||
assert set(agent.get_active_tool_names()) == fixed_toolset
|
||||
assert "s.mem" in agent.get_repl().entrypoint.overview()
|
||||
|
||||
# after activation: the toolset is unchanged, the REPL reflects the project's API exclusions
|
||||
agent.activate_project_from_path_or_name(name)
|
||||
assert set(agent.get_active_tool_names()) == fixed_toolset
|
||||
assert "s.mem" not in agent.get_repl().entrypoint.overview()
|
||||
finally:
|
||||
agent.on_shutdown(timeout=5)
|
||||
|
||||
|
||||
def _make_config_with_project(
|
||||
project_name: str,
|
||||
language_backend: LanguageBackend | None = None,
|
||||
|
||||
@@ -14,7 +14,7 @@ from _pytest.mark import Mark, MarkDecorator, ParameterSet
|
||||
|
||||
from serena.agent import SerenaAgent
|
||||
from serena.config.context_mode import SerenaAgentContext
|
||||
from serena.config.serena_config import ProjectConfig, RegisteredProject, SerenaConfig
|
||||
from serena.config.serena_config import AgentInterface, ProjectConfig, RegisteredProject, SerenaConfig
|
||||
from serena.lsp.lsp_diagnostics import DiagnosticsContext
|
||||
from serena.project import Project
|
||||
from serena.tools import (
|
||||
@@ -30,6 +30,7 @@ from serena.tools import (
|
||||
ReplaceInFilesTool,
|
||||
ReplaceSymbolBodyTool,
|
||||
SafeDeleteSymbol,
|
||||
SerenaReplTool,
|
||||
Tool,
|
||||
)
|
||||
from solidlsp.ls_config import LanguageServerId
|
||||
@@ -901,6 +902,25 @@ class TestSerenaAgent:
|
||||
finally:
|
||||
agent.on_shutdown(timeout=5)
|
||||
|
||||
@pytest.mark.python
|
||||
@pytest.mark.skipif(not language_server_tests_enabled(LanguageServerId.PYTHON), reason="python tests are disabled in this environment")
|
||||
@pytest.mark.parametrize("context_name", ["desktop-app", "grok"], ids=["multi_project", "single_project"])
|
||||
def test_repl_interface_exposes_fixed_toolset(self, serena_config, context_name: str):
|
||||
# the toolset is fixed regardless of tool inclusions/exclusions (e.g. the context's or the configuration's);
|
||||
# only the single-project property of the context matters (no project activation in that case)
|
||||
serena_config.agent_interface = AgentInterface.REPL
|
||||
serena_config.included_optional_tools = ["get_diagnostics_for_symbol"]
|
||||
context = SerenaAgentContext.from_name(context_name)
|
||||
agent = SerenaAgent(project="test_repo_python", serena_config=serena_config, context=context)
|
||||
agent.execute_task(lambda: None)
|
||||
try:
|
||||
exposed = {tool.get_name() for tool in agent.get_exposed_tool_instances()}
|
||||
expected = {"serena_repl", "initial_instructions"} | (set() if context.single_project else {"activate_project"})
|
||||
assert exposed == expected
|
||||
assert "s.lsp" in agent.get_tool(SerenaReplTool).apply("s.info()")
|
||||
finally:
|
||||
agent.on_shutdown(timeout=5)
|
||||
|
||||
def _symbol_matches_expected_name(self, symbol: dict, expected_name: str) -> bool:
|
||||
return (
|
||||
symbol.get("name") == expected_name
|
||||
|
||||
Reference in new issue
Block a user