mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-13 16:15:29 +00:00
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:
1 parent
a46a465773
commit
07b6831c70
13 files changed
+201
-79
No files matched your search
@@ -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).
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in new issue
Block a user