Record the correspondence between API methods and tools

FacadeMethodInfo gains corresponding_tool (the tool class offering the same functionality) and
get_corresponding_tool_name, and every API method with a tool counterpart names it in its
facade_method decorator. This enables applying tool-level exclusions to API methods and rendering
prompt conditions based on tools.

Since the API modules now refer to the tool classes, the tools import the APIs locally (in the
mixins' _api methods), and the symbol tools' symbol_dict_grouper attributes become properties.
This commit is contained in:
Dominik Jain authored and Dominik Jain committed 2026-09-15 12:50:44 +02:00
1 parent a46a465773
commit 07b6831c70
13 files changed
+201 -79

No files matched your search

+18 -8
View File
@@ -6,6 +6,16 @@ The implementation of editing operations, which are independent of the language
from typing import TYPE_CHECKING, Literal
from serena.code_editor import EditedFileContext
from serena.tools import (
DeleteLinesTool,
InsertAfterSymbolTool,
InsertAtLineTool,
InsertBeforeSymbolTool,
ReplaceContentTool,
ReplaceInFilesTool,
ReplaceLinesTool,
ReplaceSymbolBodyTool,
)
from serena.util.text_utils import ContentReplacer, MultiFileReplacement, ReplacementOccurrence, ReplacementRejectedError
from ..facade import SUCCESS_RESULT, FacadeApi, facade_method
@@ -68,7 +78,7 @@ class EditApi(FacadeApi):
# file-level operations
@facade_method(can_edit=True)
@facade_method(can_edit=True, corresponding_tool=ReplaceContentTool)
def replace_content(
self,
relative_path: str,
@@ -106,7 +116,7 @@ class EditApi(FacadeApi):
context.set_updated_content(replacer.replace(context.get_original_content(), needle, repl))
return SUCCESS_RESULT
@facade_method(can_edit=True)
@facade_method(can_edit=True, corresponding_tool=ReplaceInFilesTool)
def replace_in_files(
self,
needle: str,
@@ -186,7 +196,7 @@ class EditApi(FacadeApi):
# line-level operations
@facade_method(optional=True, can_edit=True)
@facade_method(optional=True, can_edit=True, corresponding_tool=DeleteLinesTool)
def delete_lines(self, relative_path: str, start_line: int, end_line: int) -> str:
"""
Deletes the given lines in the file.
@@ -200,7 +210,7 @@ class EditApi(FacadeApi):
self._create_code_editor().delete_lines(relative_path, start_line, end_line)
return SUCCESS_RESULT
@facade_method(optional=True, can_edit=True)
@facade_method(optional=True, can_edit=True, corresponding_tool=ReplaceLinesTool)
def replace_lines(self, relative_path: str, start_line: int, end_line: int, content: str) -> str:
"""
Replaces the given range of lines in the given file.
@@ -217,7 +227,7 @@ class EditApi(FacadeApi):
code_editor.insert_at_line(relative_path, start_line, self._normalize_inserted_content(content))
return SUCCESS_RESULT
@facade_method(optional=True, can_edit=True)
@facade_method(optional=True, can_edit=True, corresponding_tool=InsertAtLineTool)
def insert_at_line(self, relative_path: str, line: int, content: str) -> str:
"""
Inserts the given content at the given line in the file, pushing existing content of the line down.
@@ -239,7 +249,7 @@ class EditApi(FacadeApi):
# symbol-level operations
@facade_method(can_edit=True)
@facade_method(can_edit=True, corresponding_tool=ReplaceSymbolBodyTool)
def replace_symbol_body(self, name_path: str, relative_path: str, body: str) -> str:
"""
Replaces the body of the given symbol.
@@ -257,7 +267,7 @@ class EditApi(FacadeApi):
self._create_code_editor().replace_body(name_path, relative_file_path=relative_path, body=body)
return SUCCESS_RESULT
@facade_method(can_edit=True)
@facade_method(can_edit=True, corresponding_tool=InsertAfterSymbolTool)
def insert_after_symbol(self, name_path: str, relative_path: str, body: str) -> str:
"""
Inserts code after a class/method/function definition.
@@ -272,7 +282,7 @@ class EditApi(FacadeApi):
self._create_code_editor().insert_after_symbol(name_path, relative_file_path=relative_path, body=body)
return SUCCESS_RESULT
@facade_method(can_edit=True)
@facade_method(can_edit=True, corresponding_tool=InsertBeforeSymbolTool)
def insert_before_symbol(self, name_path: str, relative_path: str, body: str) -> str:
"""
Inserts the given content before the beginning of the definition of the given symbol (via the symbol's location).
+6 -5
View File
@@ -9,6 +9,7 @@ from fnmatch import fnmatch
from pathlib import Path
from typing import TYPE_CHECKING
from serena.tools import CreateTextFileTool, FindFileTool, ListDirTool, ReadFileTool, SearchForPatternTool
from serena.util.file_system import scan_directory
from serena.util.text_utils import MatchedConsecutiveLines
from solidlsp.ls_utils import TextUtils
@@ -163,7 +164,7 @@ class FsApi(FacadeApi):
def __init__(self, agent: "SerenaAgent") -> None:
super().__init__(agent, name="fs", description="the project's files as units (as opposed to their content, see `edit`)")
@facade_method()
@facade_method(corresponding_tool=ReadFileTool)
def read_file(self, relative_path: str, start_line: int = 0, end_line: int | None = None, max_answer_chars: int = -1) -> FileContent:
"""
Reads the given file or a range of its lines.
@@ -183,7 +184,7 @@ class FsApi(FacadeApi):
lines = lines[start_line:] if end_line is None else lines[start_line : end_line + 1]
return FileContent(lines, FileContentRenderer(self._agent, max_answer_chars))
@facade_method(can_edit=True)
@facade_method(can_edit=True, corresponding_tool=CreateTextFileTool)
def create_text_file(self, relative_path: str, content: str) -> str:
"""
Writes a new file or overwrites an existing file with the given content.
@@ -211,7 +212,7 @@ class FsApi(FacadeApi):
answer += " Overwrote existing file."
return answer
@facade_method()
@facade_method(corresponding_tool=ListDirTool)
def list_dir(
self, relative_path: str, recursive: bool, skip_ignored_files: bool = False, max_answer_chars: int = -1
) -> DirectoryListing:
@@ -240,7 +241,7 @@ class FsApi(FacadeApi):
)
return DirectoryListing(dirs, files, DirectoryListingRenderer(self._agent, max_answer_chars))
@facade_method()
@facade_method(corresponding_tool=FindFileTool)
def find_file(self, file_mask: str, relative_path: str) -> list[str]:
"""
Finds files matching the given file mask within the given relative path.
@@ -269,7 +270,7 @@ class FsApi(FacadeApi):
)
return files
@facade_method()
@facade_method(corresponding_tool=SearchForPatternTool)
def search_for_pattern(
self,
substring_pattern: str,
+28 -13
View File
@@ -13,6 +13,21 @@ from serena.code_editor import JetBrainsCodeEditor
from serena.jetbrains.jetbrains_plugin_client import JetBrainsPluginClient
from serena.jetbrains.jetbrains_types import SymbolDTO, SymbolDTOUtil
from serena.symbol import JetBrainsSymbolDictGrouper
from serena.tools import (
JetBrainsDebugTool,
JetBrainsFindDeclarationTool,
JetBrainsFindImplementationsTool,
JetBrainsFindReferencingSymbolsTool,
JetBrainsFindSymbolTool,
JetBrainsGetSymbolsOverviewTool,
JetBrainsInlineSymbol,
JetBrainsListInspectionsTool,
JetBrainsMoveTool,
JetBrainsRenameTool,
JetBrainsRunInspectionsTool,
JetBrainsSafeDeleteTool,
JetBrainsTypeHierarchyTool,
)
from serena.util.text_utils import find_text_coordinates
from ..facade import FacadeApi, facade_method
@@ -177,7 +192,7 @@ class JetBrainsApi(FacadeApi):
# read operations
@facade_method()
@facade_method(corresponding_tool=JetBrainsFindSymbolTool)
def find_symbol(
self,
name_path_pattern: str,
@@ -261,7 +276,7 @@ class JetBrainsApi(FacadeApi):
raise ValueError(f"Matched {n_matches}>{max_matches=} symbols.\n" + renderer.render_identifiers(collection))
return collection
@facade_method()
@facade_method(corresponding_tool=JetBrainsFindReferencingSymbolsTool)
def find_referencing_symbols(self, name_path: str, relative_path: str, max_answer_chars: int = -1) -> JetBrainsSymbolCollection:
"""
Finds all symbols that reference the given symbol — its callers / usages / dependents, i.e. the
@@ -294,7 +309,7 @@ class JetBrainsApi(FacadeApi):
renderer = JetBrainsReferencesRenderer(self._agent, max_answer_chars, grouper=self.references_grouper_)
return JetBrainsSymbolCollection(symbol_dicts, renderer)
@facade_method()
@facade_method(corresponding_tool=JetBrainsGetSymbolsOverviewTool)
def get_symbols_overview(
self, relative_path: str, depth: int = -1, max_answer_chars: int = -1, include_file_documentation: bool = False
) -> JetBrainsSymbolsOverview:
@@ -340,7 +355,7 @@ class JetBrainsApi(FacadeApi):
result[rel_path].append(name_path)
return dict(result)
@facade_method()
@facade_method(corresponding_tool=JetBrainsTypeHierarchyTool)
def get_type_hierarchy(
self,
name_path: str,
@@ -379,7 +394,7 @@ class JetBrainsApi(FacadeApi):
result["levels_not_included"] = levels_not_included
return self._json_object(result, max_answer_chars)
@facade_method()
@facade_method(corresponding_tool=JetBrainsFindDeclarationTool)
def find_declaration(self, relative_path: str, regex: str, include_body: bool = False) -> JetBrainsSymbolCollection:
r"""
Finds the declaration of a symbol.
@@ -402,7 +417,7 @@ class JetBrainsApi(FacadeApi):
)
return JetBrainsSymbolCollection(response["symbols"], JetBrainsSymbolCollectionRenderer(self._agent, -1))
@facade_method()
@facade_method(corresponding_tool=JetBrainsFindImplementationsTool)
def find_implementations(self, relative_path: str, name_path: str) -> JetBrainsSymbolCollection:
"""
Finds the implementations of a symbol.
@@ -417,7 +432,7 @@ class JetBrainsApi(FacadeApi):
# edit operations
@facade_method(can_edit=True)
@facade_method(can_edit=True, corresponding_tool=JetBrainsRenameTool)
def rename(
self,
relative_path: str,
@@ -448,7 +463,7 @@ class JetBrainsApi(FacadeApi):
)
return self._json_object(result)
@facade_method(beta=True, can_edit=True)
@facade_method(beta=True, can_edit=True, corresponding_tool=JetBrainsMoveTool)
def move(
self,
relative_path: str,
@@ -489,7 +504,7 @@ class JetBrainsApi(FacadeApi):
)
return self._json_object(result)
@facade_method(beta=True, can_edit=True)
@facade_method(beta=True, can_edit=True, corresponding_tool=JetBrainsSafeDeleteTool)
def safe_delete(
self, relative_path: str, name_path: str | None = None, delete_even_if_used: bool = False, propagate: bool = False
) -> JsonObject:
@@ -516,7 +531,7 @@ class JetBrainsApi(FacadeApi):
)
return self._json_object(result)
@facade_method(beta=True, can_edit=True)
@facade_method(beta=True, can_edit=True, corresponding_tool=JetBrainsInlineSymbol)
def inline_symbol(self, name_path: str, relative_path: str, keep_definition: bool = False) -> JsonObject:
"""
Inlines a symbol (usually a method/function, but also classes may be amenable to inlining,
@@ -537,7 +552,7 @@ class JetBrainsApi(FacadeApi):
# inspections
@facade_method()
@facade_method(corresponding_tool=JetBrainsRunInspectionsTool)
def run_inspections(
self,
relative_path: str,
@@ -573,7 +588,7 @@ class JetBrainsApi(FacadeApi):
)
return self._json_object(result, max_answer_chars)
@facade_method()
@facade_method(corresponding_tool=JetBrainsListInspectionsTool)
def list_inspections(
self, language: str | None = None, group_path_contains: str | None = None, max_answer_chars: int = -1
) -> JsonObject:
@@ -603,7 +618,7 @@ class JetBrainsApi(FacadeApi):
"""
return self._agent.prompt_factory.create_info_jet_brains_debug_repl()
@facade_method(beta=True)
@facade_method(beta=True, corresponding_tool=JetBrainsDebugTool)
def debug_eval(self, expression: str, repl_key: str = "default") -> str:
"""
Provides debugging functionality (run configs, breakpoints, stepping, inspection, and evaluation)
+22 -10
View File
@@ -18,6 +18,18 @@ from serena.symbol import (
ReferenceInLanguageServerSymbol,
SymbolDictGrouper,
)
from serena.tools import (
FindDeclarationTool,
FindImplementationsTool,
FindReferencingSymbolsTool,
FindSymbolTool,
GetDiagnosticsForFileTool,
GetDiagnosticsForSymbolTool,
GetSymbolsOverviewTool,
RenameSymbolTool,
RestartLanguageServerTool,
SafeDeleteSymbol,
)
from serena.util.text_utils import TextOutputUtils, find_text_coordinates
from solidlsp.lsp_protocol_handler.lsp_types import SymbolKind
@@ -310,7 +322,7 @@ class LspApi(FacadeApi):
# language server management
@facade_method(optional=True)
@facade_method(optional=True, corresponding_tool=RestartLanguageServerTool)
def restart_language_server(self) -> str:
"""
Restarts the language server(s). Use this only on explicit user request or after confirmation;
@@ -323,7 +335,7 @@ class LspApi(FacadeApi):
# read operations
@facade_method()
@facade_method(corresponding_tool=GetSymbolsOverviewTool)
def get_symbols_overview(self, relative_path: str, depth: int = -1, max_answer_chars: int = -1) -> LspSymbolCollection:
"""
Gets an overview of the top-level symbols defined in the given file (classes, methods, fields) — its
@@ -369,7 +381,7 @@ class LspApi(FacadeApi):
)
return LspSymbolCollection(symbols, renderer)
@facade_method()
@facade_method(corresponding_tool=FindSymbolTool)
def find_symbol(
self,
name_path_pattern: str,
@@ -462,7 +474,7 @@ class LspApi(FacadeApi):
return symbol_collection
@facade_method()
@facade_method(corresponding_tool=FindReferencingSymbolsTool)
def find_referencing_symbols(
self,
name_path: str,
@@ -496,7 +508,7 @@ class LspApi(FacadeApi):
)
return LspReferenceCollection(references, LspReferenceCollectionRenderer(self._agent, max_answer_chars, self.references_grouper_))
@facade_method()
@facade_method(corresponding_tool=FindImplementationsTool)
def find_implementations(
self,
name_path: str,
@@ -532,7 +544,7 @@ class LspApi(FacadeApi):
output_params = SymbolOutputParams(kind=True, relative_path=True, body_location=True, include_info=include_info)
return LspSymbolCollection(symbols, LspSymbolCollectionRenderer(self._agent, max_answer_chars, symbol_retriever, output_params))
@facade_method()
@facade_method(corresponding_tool=FindDeclarationTool)
def find_declaration(
self,
relative_path: str,
@@ -587,7 +599,7 @@ class LspApi(FacadeApi):
collection_renderer = LspSymbolCollectionRenderer(self._agent, -1, symbol_retriever, output_params)
return LspSymbol(defining_symbol, LspSymbolRenderer(self._agent, -1, collection_renderer))
@facade_method()
@facade_method(corresponding_tool=GetDiagnosticsForFileTool)
def get_diagnostics_for_file(
self, relative_path: str, start_line: int = 0, end_line: int = -1, min_severity: int = 4, max_answer_chars: int = -1
) -> LspDiagnostics:
@@ -621,7 +633,7 @@ class LspApi(FacadeApi):
return self._create_diagnostics(grouped_diagnostics, max_answer_chars)
@facade_method(optional=True)
@facade_method(optional=True, corresponding_tool=GetDiagnosticsForSymbolTool)
def get_diagnostics_for_symbol(
self,
name_path: str,
@@ -665,7 +677,7 @@ class LspApi(FacadeApi):
# edit operations
@facade_method(can_edit=True)
@facade_method(can_edit=True, corresponding_tool=RenameSymbolTool)
def rename_symbol(self, name_path: str, relative_path: str, new_name: str) -> str:
"""
Renames the symbol with the given `name_path` to `new_name` throughout the entire codebase.
@@ -680,7 +692,7 @@ class LspApi(FacadeApi):
self._get_project().ls_sync_file_system_changes()
return self._create_ls_code_editor().rename_symbol(name_path, relative_path=relative_path, new_name=new_name)
@facade_method(can_edit=True)
@facade_method(can_edit=True, corresponding_tool=SafeDeleteSymbol)
def safe_delete_symbol(self, name_path_pattern: str, relative_path: str) -> str:
"""
Deletes the symbol if it is safe to do so (i.e., if there are no references to it)
+16 -7
View File
@@ -8,6 +8,15 @@ import platform
from typing import TYPE_CHECKING, Literal
from serena.memories.memory_manager import MemoryManager
from serena.tools import (
DeleteMemoryTool,
EditMemoryTool,
ListMemoriesTool,
OnboardingTool,
ReadMemoryTool,
RenameMemoryTool,
WriteMemoryTool,
)
from ..facade import FacadeApi, facade_method
from ..representable import Renderer, RepresentableViaRenderer
@@ -56,7 +65,7 @@ class MemoryApi(FacadeApi):
def _get_memory_manager(self) -> MemoryManager:
return self._get_project().memory_manager
@facade_method()
@facade_method(corresponding_tool=ListMemoriesTool)
def list_memories(self, topic: str = "") -> MemoryList:
"""
Lists the available memories, optionally filtered by topic.
@@ -66,7 +75,7 @@ class MemoryApi(FacadeApi):
"""
return MemoryList(self._get_memory_manager().list_memories(topic), MemoryListRenderer(self._agent, -1))
@facade_method()
@facade_method(corresponding_tool=ReadMemoryTool)
def read_memory(self, memory_name: str) -> str:
"""
Reads a memory that is likely to be relevant to the current task, inferring relevance e.g. from the name.
@@ -76,7 +85,7 @@ class MemoryApi(FacadeApi):
"""
return self._get_memory_manager().load_memory(memory_name)
@facade_method(can_edit=True)
@facade_method(can_edit=True, corresponding_tool=WriteMemoryTool)
def write_memory(self, memory_name: str, content: str, max_chars: int = -1) -> str:
"""
Writes information about this project that can be useful for future tasks in md format.
@@ -98,7 +107,7 @@ class MemoryApi(FacadeApi):
)
return self._get_memory_manager().save_memory(memory_name, content, is_tool_context=True)
@facade_method(can_edit=True)
@facade_method(can_edit=True, corresponding_tool=EditMemoryTool)
def edit_memory(
self,
memory_name: str,
@@ -125,7 +134,7 @@ class MemoryApi(FacadeApi):
memory_name, needle, repl, mode, allow_multiple_occurrences, is_tool_context=True, regex_multiline=True
)
@facade_method(can_edit=True)
@facade_method(can_edit=True, corresponding_tool=RenameMemoryTool)
def rename_memory(self, old_name: str, new_name: str) -> str:
"""
Renames or moves a memory; use "/" in the name to organize into topics.
@@ -144,7 +153,7 @@ class MemoryApi(FacadeApi):
log.info(f"Updated {n_references_updated} references to memory {old_name} to {new_name}")
return renaming_message
@facade_method(can_edit=True)
@facade_method(can_edit=True, corresponding_tool=DeleteMemoryTool)
def delete_memory(self, memory_name: str) -> str:
"""
Deletes a memory; only call this if instructed explicitly or permission was granted by the user.
@@ -154,7 +163,7 @@ class MemoryApi(FacadeApi):
"""
return self._get_memory_manager().delete_memory(memory_name, is_tool_context=True)
@facade_method()
@facade_method(corresponding_tool=OnboardingTool)
def onboarding(self) -> str:
"""
Provides the instructions for performing onboarding (identifying the project structure and essential tasks,
+2 -1
View File
@@ -6,6 +6,7 @@ The implementation of shell command execution.
import os.path
from typing import TYPE_CHECKING
from serena.tools import ExecuteShellCommandTool
from serena.util.shell import ShellCommandResult, execute_shell_command
from ..facade import FacadeApi, facade_method
@@ -54,7 +55,7 @@ class ShellApi(FacadeApi):
def __init__(self, agent: "SerenaAgent") -> None:
super().__init__(agent, name="shell", description="execution of shell commands")
@facade_method(can_edit=True)
@facade_method(can_edit=True, corresponding_tool=ExecuteShellCommandTool)
def execute_shell_command(
self, command: str, cwd: str | None = None, capture_stderr: bool = True, max_answer_chars: int = -1
) -> ShellCommandOutput:
+17 -2
View File
@@ -17,6 +17,7 @@ from serena.project import Project
if TYPE_CHECKING:
from serena.agent import SerenaAgent
from serena.code_editor import CodeEditor
from serena.tools import Tool
log = logging.getLogger(__name__)
TCallable = TypeVar("TCallable", bound=Callable[..., Any])
@@ -39,12 +40,22 @@ class FacadeMethodInfo:
"""whether the method is in beta (not yet fully stable)"""
can_edit: bool = False
"""whether the method can modify the codebase (relevant for read-only contexts)"""
corresponding_tool: "type[Tool] | None" = None
"""the classic tool offering the same functionality, if any"""
def get_corresponding_tool_name(self) -> str | None:
"""
:return: the name of the corresponding tool, or None if there is none
"""
return self.corresponding_tool.get_name_from_cls() if self.corresponding_tool is not None else None
_FACADE_METHOD_INFO_ATTR = "__facade_method_info__"
def facade_method(*, optional: bool = False, beta: bool = False, can_edit: bool = False) -> Callable[[TCallable], TCallable]:
def facade_method(
*, optional: bool = False, beta: bool = False, can_edit: bool = False, corresponding_tool: "type[Tool] | None" = None
) -> Callable[[TCallable], TCallable]:
"""
Marks a method of a `FacadeApi` as exposed through the facade, attaching the given metadata.
The decorator only annotates the method (it does not wrap it), such that signature and docstring remain intact.
@@ -52,11 +63,15 @@ def facade_method(*, optional: bool = False, beta: bool = False, can_edit: bool
:param optional: whether the method is disabled by default and must be enabled explicitly
:param beta: whether the method is in beta
:param can_edit: whether the method can modify the codebase
:param corresponding_tool: the classic tool offering the same functionality, if any
:return: the decorator
"""
def decorator(method: TCallable) -> TCallable:
setattr(method, _FACADE_METHOD_INFO_ATTR, FacadeMethodInfo(name=method.__name__, optional=optional, beta=beta, can_edit=can_edit))
info = FacadeMethodInfo(
name=method.__name__, optional=optional, beta=beta, can_edit=can_edit, corresponding_tool=corresponding_tool
)
setattr(method, _FACADE_METHOD_INFO_ATTR, info)
return method
return decorator
+9 -4
View File
@@ -3,18 +3,23 @@ Tools supporting the execution of (external) commands
"""
# SPDX-License-Identifier: GPL-3.0-or-later
from typing import cast
from typing import TYPE_CHECKING, cast
from serena.repl.api.shell_api import ShellApi
from serena.tools import Tool, ToolMarkerCanEdit
if TYPE_CHECKING:
from serena.repl.api.shell_api import ShellApi
class ShellApiMixin:
"""
Mixin for tools which delegate to the shell API
Mixin for tools which delegate to the shell API.
The API is imported locally, since the API module refers to the tools (as corresponding tools).
"""
def _api(self) -> ShellApi:
def _api(self) -> "ShellApi":
from serena.repl.api.shell_api import ShellApi
tool = cast(Tool, cast(object, self))
return ShellApi(tool.agent)
+15 -7
View File
@@ -7,29 +7,37 @@ File and file system-related tools, specifically for
"""
# SPDX-License-Identifier: GPL-3.0-or-later
from typing import Literal, cast
from typing import TYPE_CHECKING, Literal, cast
from serena.repl.api.edit_api import EditApi
from serena.repl.api.fs_api import FsApi
from serena.tools import EditingToolWithDiagnostics, Tool, ToolMarkerOptional
if TYPE_CHECKING:
from serena.repl.api.edit_api import EditApi
from serena.repl.api.fs_api import FsApi
class EditApiMixin:
"""
Mixin for tools which delegate to the editing API
Mixin for tools which delegate to the editing API.
The API is imported locally, since the API module refers to the tools (as corresponding tools).
"""
def _api(self) -> EditApi:
def _api(self) -> "EditApi":
from serena.repl.api.edit_api import EditApi
tool = cast(Tool, cast(object, self))
return EditApi(tool.agent)
class FsApiMixin:
"""
Mixin for tools which delegate to the file system API
Mixin for tools which delegate to the file system API.
The API is imported locally, since the API module refers to the tools (as corresponding tools).
"""
def _api(self) -> FsApi:
def _api(self) -> "FsApi":
from serena.repl.api.fs_api import FsApi
tool = cast(Tool, cast(object, self))
return FsApi(tool.agent)
+24 -10
View File
@@ -1,26 +1,28 @@
# SPDX-License-Identifier: GPL-3.0-or-later
import logging
from typing import TYPE_CHECKING, Literal
from typing import TYPE_CHECKING, Literal, cast
from serena.repl.api.jb_api import JetBrainsApi
from serena.symbol import SymbolDictGrouper
from serena.tools import Tool, ToolMarkerBeta, ToolMarkerOptional, ToolMarkerSymbolicEdit, ToolMarkerSymbolicRead
if TYPE_CHECKING:
from serena.agent import SerenaAgent
from serena.repl.api.jb_api import JetBrainsApi
log = logging.getLogger(__name__)
class JetBrainsApiMixin:
"""
Mixin for tools which delegate to the JetBrains API
Mixin for tools which delegate to the JetBrains API.
The API is imported locally, since the API module refers to the tools (as corresponding tools).
"""
agent: "SerenaAgent"
def _api(self) -> "JetBrainsApi":
from serena.repl.api.jb_api import JetBrainsApi
def _api(self) -> JetBrainsApi:
return JetBrainsApi(self.agent)
tool = cast(Tool, cast(object, self))
return JetBrainsApi(tool.agent)
class JetBrainsFindSymbolTool(Tool, ToolMarkerSymbolicRead, ToolMarkerOptional, JetBrainsApiMixin):
@@ -28,7 +30,11 @@ class JetBrainsFindSymbolTool(Tool, ToolMarkerSymbolicRead, ToolMarkerOptional,
Performs a global (or local) search for symbols using the JetBrains backend
"""
symbol_dict_grouper = JetBrainsApi.find_symbol_grouper_
@property
def symbol_dict_grouper(self) -> SymbolDictGrouper:
from serena.repl.api.jb_api import JetBrainsApi
return JetBrainsApi.find_symbol_grouper_
def apply(
self,
@@ -217,7 +223,11 @@ class JetBrainsFindReferencingSymbolsTool(Tool, ToolMarkerSymbolicRead, ToolMark
Finds symbols that reference the given symbol using the JetBrains backend
"""
symbol_dict_grouper = JetBrainsApi.references_grouper_
@property
def symbol_dict_grouper(self) -> SymbolDictGrouper:
from serena.repl.api.jb_api import JetBrainsApi
return JetBrainsApi.references_grouper_
def apply(
self,
@@ -245,7 +255,11 @@ class JetBrainsGetSymbolsOverviewTool(Tool, ToolMarkerSymbolicRead, ToolMarkerOp
Retrieves an overview of the top-level symbols within a specified file using the JetBrains backend
"""
symbol_dict_grouper = JetBrainsApi.overview_grouper_
@property
def symbol_dict_grouper(self) -> SymbolDictGrouper:
from serena.repl.api.jb_api import JetBrainsApi
return JetBrainsApi.overview_grouper_
def apply(
self,
+9 -4
View File
@@ -1,17 +1,22 @@
# SPDX-License-Identifier: GPL-3.0-or-later
from typing import Literal, cast
from typing import TYPE_CHECKING, Literal, cast
from serena.repl.api.mem_api import MemoryApi
from serena.tools import Tool, ToolMarkerCanEdit
if TYPE_CHECKING:
from serena.repl.api.mem_api import MemoryApi
class MemoryApiMixin:
"""
Mixin for tools which delegate to the memory API
Mixin for tools which delegate to the memory API.
The API is imported locally, since the API module refers to the tools (as corresponding tools).
"""
def _api(self) -> MemoryApi:
def _api(self) -> "MemoryApi":
from serena.repl.api.mem_api import MemoryApi
tool = cast(Tool, cast(object, self))
return MemoryApi(tool.agent)
+25 -7
View File
@@ -3,9 +3,9 @@ Language server-related tools
"""
# SPDX-License-Identifier: GPL-3.0-or-later
from typing import cast
from typing import TYPE_CHECKING, cast
from serena.repl.api.lsp_api import LspApi
from serena.symbol import SymbolDictGrouper
from serena.tools import (
EditingToolWithDiagnostics,
Tool,
@@ -15,13 +15,19 @@ from serena.tools import (
from serena.tools.file_tools import EditApiMixin
from serena.tools.tools_base import ToolMarkerOptional
if TYPE_CHECKING:
from serena.repl.api.lsp_api import LspApi
class LspApiMixin:
"""
Mixin for tools which delegate to the language server API
Mixin for tools which delegate to the language server API.
The API is imported locally, since the API module refers to the tools (as corresponding tools).
"""
def _api(self) -> LspApi:
def _api(self) -> "LspApi":
from serena.repl.api.lsp_api import LspApi
tool = cast(Tool, cast(object, self))
return LspApi(tool.agent)
@@ -41,7 +47,11 @@ class GetSymbolsOverviewTool(Tool, ToolMarkerSymbolicRead, LspApiMixin):
Gets an overview of the top-level symbols defined in a given file.
"""
symbol_dict_grouper = LspApi.overview_grouper_
@property
def symbol_dict_grouper(self) -> SymbolDictGrouper:
from serena.repl.api.lsp_api import LspApi
return LspApi.overview_grouper_
def apply(self, relative_path: str, depth: int = -1, max_answer_chars: int = -1) -> str:
"""
@@ -65,7 +75,11 @@ class FindSymbolTool(Tool, ToolMarkerSymbolicRead, LspApiMixin):
Performs a global (or local) search using the language server backend.
"""
symbol_dict_grouper = LspApi.find_symbol_dict_grouper_
@property
def symbol_dict_grouper(self) -> SymbolDictGrouper:
from serena.repl.api.lsp_api import LspApi
return LspApi.find_symbol_dict_grouper_
def apply(
self,
@@ -146,7 +160,11 @@ class FindReferencingSymbolsTool(Tool, ToolMarkerSymbolicRead, LspApiMixin):
Finds symbols that reference the given symbol
"""
symbol_dict_grouper = LspApi.references_grouper_
@property
def symbol_dict_grouper(self) -> SymbolDictGrouper:
from serena.repl.api.lsp_api import LspApi
return LspApi.references_grouper_
def apply(
self,
+10 -1
View File
@@ -11,7 +11,7 @@ from serena.config.serena_config import ApiInclusionDefinition
from serena.repl.api.lsp_api import LspApi
from serena.repl.facade import ApiScope, Facade, FacadeApi, FacadeMethodInfo, facade_method
from serena.repl.repl import SerenaRepl
from serena.tools import SerenaReplTool
from serena.tools import FindSymbolTool, SerenaReplTool
from solidlsp.ls_config import LanguageServerId
from test.conftest import agent_for_project_context
@@ -165,6 +165,15 @@ class TestFacade:
facade = Facade.from_api(self.DummyApi(MagicMock()), self._scope(included_apis=["dummy.extra"]))
assert "extra" in facade.enabled_method_names
def test_corresponding_tool(self) -> None:
facade = Facade.from_api(self.DummyApi(MagicMock()), ApiScope())
assert facade.get_method("add").info.get_corresponding_tool_name() is None
lsp_facade = Facade.from_api(LspApi(MagicMock()), ApiScope())
info = lsp_facade.get_method("find_symbol").info
assert info.corresponding_tool is FindSymbolTool
assert info.get_corresponding_tool_name() == "find_symbol"
def test_method_info_mirrors_decorator(self) -> None:
facade = Facade.from_api(self.DummyApi(MagicMock()), ApiScope())
assert facade.get_method("add").info == FacadeMethodInfo(name="add")