From 9abfc3d1be673b07bf366e76effcfa98d86ce6db Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Fri, 12 Jun 2026 19:40:12 +0200 Subject: [PATCH] Add a tool name replacement mapping for the generation of prompts --- CHANGELOG.md | 5 +-- pyproject.toml | 1 + src/serena/agent.py | 32 ++++++++++++++++++- src/serena/config/serena_config.py | 23 +++++++++++++ .../generated/generated_prompt_factory.py | 1 + .../config/internal_modes/jetbrains.yml | 8 +---- src/serena/resources/config/modes/editing.yml | 4 +-- .../config/prompt_templates/system_prompt.yml | 10 +++--- 8 files changed, 67 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ba7a287..b7cb5bc3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,8 +14,9 @@ Status of the `main` branch. Changes prior to the next official version change w incorrect file access if a corresponding local file existed (e.g. `./antigravity` binary); file access is now guarded with path detection (file ending or path separator must be present) - Allow `query_project` tool to access read-only tools that are not enabled in the current configuration - - Fix: JetBrains mode prompt was not provided to agents; The mode is now treated as a (background) base mode - in `ActiveModes` which reduces the surface for issues pertaining to custom handling of modes. + - Adjust prompt generation mechanism to use newly introduced tool name mapping `tool_names`, allowing + prompts to directly use tool names that match the active language backend (and removing the need + for additional prompts that explain tool name differences) - Improve quoting/escaping of arguments in shell executions on Windows (via `oslex` dependency) * Language Servers: diff --git a/pyproject.toml b/pyproject.toml index ca221c0a..f2c8059f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -284,6 +284,7 @@ ignore = [ "UP042", # wants str,Enum -> StrEnum (breaking change) "PLW0108", # unnecessary lambda (style preference) "PLC0207", # split vs rsplit optimization (style preference) + "RSE102", # wants to force use of raise with class instead of instance (style preference) ] unfixable = ["F841", "F601", "F602", "B018"] extend-fixable = ["F401", "B905", "W291"] diff --git a/src/serena/agent.py b/src/serena/agent.py index 5a151377..1160b737 100644 --- a/src/serena/agent.py +++ b/src/serena/agent.py @@ -647,6 +647,9 @@ class SerenaAgent: else: log.info(f"Using language backend from global configuration: {self._language_backend.name}") + # create the tool names mapping for prompts + self._prompt_tool_names_mapping = self._create_prompt_tool_names_mapping(self._language_backend) + # create executor for starting the language server and running tools in another thread # This executor is used to achieve linear task execution self._task_executor = TaskExecutor("SerenaAgentTaskExecutor", self._task_completion_callback) @@ -927,9 +930,35 @@ class SerenaAgent: """ return self._active_modes + @staticmethod + def _create_prompt_tool_names_mapping(language_backend: LanguageBackend) -> dict[str, str]: + """ + Creates a mapping from tool names to new tool names, which take into consideration + + * legacy tool names, where the name was changed and + * LSP tools which are functionally replaced by other tools due to the active language backend + (e.g. "find_symbol" being replaced by "jet_brains_find_symbol" in JetBrains mode). + + The mapping is intended to be used for the generation of prompts, such that prompts can + refer to tool names as `{{ tool_names["find_symbol"] }}`, and the mapping will ensure that + the correct tool name is used in the prompt based on the active language backend. + + :return: the mapping from tool names to new tool names + """ + result = dict(ToolSet.LEGACY_TOOL_NAME_MAPPING) + class_replacements = language_backend.get_lsp_tool_class_replacements() + for tool_class in ToolRegistry().get_all_tool_classes(): + new_tool_class: type[Tool] = class_replacements.get(tool_class, tool_class) + result[tool_class.get_name_from_cls()] = new_tool_class.get_name_from_cls() + return result + def _format_prompt(self, prompt_template: str) -> str: template = JinjaTemplate(prompt_template) - return template.render(available_tools=self._exposed_tools.tool_names, available_markers=self._exposed_tools.tool_marker_names) + return template.render( + available_tools=self._exposed_tools.tool_names, + available_markers=self._exposed_tools.tool_marker_names, + tool_names=self._prompt_tool_names_mapping, + ) def create_connection_prompt(self) -> str: """ @@ -969,6 +998,7 @@ class SerenaAgent: available_tools=available_tools.tool_names, available_markers=available_markers, global_memories_list=global_memories_str, + tool_names=self._prompt_tool_names_mapping, ) # provide the project activation message if it hasn't yet been provided diff --git a/src/serena/config/serena_config.py b/src/serena/config/serena_config.py index 15499fe3..8f2c3522 100644 --- a/src/serena/config/serena_config.py +++ b/src/serena/config/serena_config.py @@ -42,6 +42,7 @@ from ..util.dataclass import get_dataclass_default if TYPE_CHECKING: from ..project import Project + from ..tools.tools_base import Tool log = logging.getLogger(__name__) T = TypeVar("T") @@ -217,6 +218,28 @@ class LanguageBackend(Enum): def is_jetbrains(self) -> bool: return self == LanguageBackend.JETBRAINS + def get_lsp_tool_class_replacements(self) -> "dict[type[Tool], type[Tool]]": + """ + :return: mapping from LSP tool classes to replacement tool classes (functional replacements) + """ + match self: + case LanguageBackend.LSP: + return {} + case LanguageBackend.JETBRAINS: + from ..tools import jetbrains_tools, symbol_tools + + return { + symbol_tools.FindSymbolTool: jetbrains_tools.JetBrainsFindSymbolTool, + symbol_tools.GetSymbolsOverviewTool: jetbrains_tools.JetBrainsGetSymbolsOverviewTool, + symbol_tools.FindReferencingSymbolsTool: jetbrains_tools.JetBrainsFindReferencingSymbolsTool, + symbol_tools.FindImplementationsTool: jetbrains_tools.JetBrainsFindImplementationsTool, + symbol_tools.FindDeclarationTool: jetbrains_tools.JetBrainsFindDeclarationTool, + symbol_tools.RenameSymbolTool: jetbrains_tools.JetBrainsRenameTool, + symbol_tools.SafeDeleteSymbol: jetbrains_tools.JetBrainsSafeDeleteTool, + } + case _: + raise NotImplementedError() + class LineEnding(Enum): """Line ending convention for file writes.""" diff --git a/src/serena/generated/generated_prompt_factory.py b/src/serena/generated/generated_prompt_factory.py index 2a43e586..7b0408e6 100644 --- a/src/serena/generated/generated_prompt_factory.py +++ b/src/serena/generated/generated_prompt_factory.py @@ -42,6 +42,7 @@ class PromptFactory(PromptFactoryBase): context_system_prompt: Any, global_memories_list: Any, mode_system_prompts: Any, + tool_names: Any, ) -> str: return self._render_prompt("system_prompt", locals()) diff --git a/src/serena/resources/config/internal_modes/jetbrains.yml b/src/serena/resources/config/internal_modes/jetbrains.yml index e3776dd9..e4eb218e 100644 --- a/src/serena/resources/config/internal_modes/jetbrains.yml +++ b/src/serena/resources/config/internal_modes/jetbrains.yml @@ -1,11 +1,5 @@ description: JetBrains tools replace language server-based tools -prompt: | - You have access to the very powerful JetBrains tools for symbolic operations. - These partly replace regular tools you were informed about: - * `jet_brains_find_symbol` replaces `find_symbol` - * `jet_brains_find_referencing_symbols` replaces `find_referencing_symbols` - * `jet_brains_get_symbols_overview` replaces `get_symbols_overview` - * `jet_brains_rename` replaces `rename_symbol` +prompt: excluded_tools: - find_symbol - find_referencing_symbols diff --git a/src/serena/resources/config/modes/editing.yml b/src/serena/resources/config/modes/editing.yml index 57d800b5..3c5c038c 100644 --- a/src/serena/resources/config/modes/editing.yml +++ b/src/serena/resources/config/modes/editing.yml @@ -21,9 +21,9 @@ prompt: | If you need to replace the definition of a symbol, use the `replace_symbol_body` tool. If you want to add some new code at the end of the file, use the `insert_after_symbol` tool with the last top-level symbol in the file. Similarly, you can use `insert_before_symbol` with the first top-level symbol in the file to insert code at the beginning of a file. - You can understand relationships between symbols by using the `find_referencing_symbols` tool. If not explicitly requested otherwise by the user, + You can understand relationships between symbols by using the ``{{ tool_names['find_referencing_symbols'] }}`` tool. If not explicitly requested otherwise by the user, you make sure that when you edit a symbol, the change is either backward-compatible or you find and update all references as needed. - The `find_referencing_symbols` tool will give you code snippets around the references as well as symbolic information. + The `{{ tool_names['find_referencing_symbols'] }}` tool will give you code snippets around the references as well as symbolic information. You can assume that all symbol editing tools are reliable, so you never need to verify the results if the tools return without error. {% if 'replace_content' in available_tools %} diff --git a/src/serena/resources/config/prompt_templates/system_prompt.yml b/src/serena/resources/config/prompt_templates/system_prompt.yml index 722d3a9d..c82b2bb5 100644 --- a/src/serena/resources/config/prompt_templates/system_prompt.yml +++ b/src/serena/resources/config/prompt_templates/system_prompt.yml @@ -26,14 +26,14 @@ prompts: {% endif %} {% if 'ToolMarkerSymbolicRead' in available_markers %} - Symbols are identified by their `name_path` and `relative_path` (see the description of the `find_symbol` tool). - You can get information about the symbols in a file by using the `get_symbols_overview` tool or use the `find_symbol` to search. + Symbols are identified by their `name_path` and `relative_path` (see the description of the `{{ tool_names['find_symbol'] }}` tool). + You can get information about the symbols in a file by using the `{{ tool_names['get_symbols_overview'] }}` tool or use the `{{ tool_names['find_symbol'] }}` to search. You only read the bodies of symbols when you need to (e.g. if you want to fully understand or edit it). For example, if you are working with Python code and already know that you need to read the body of the constructor of the class Foo, you can directly - use `find_symbol` with name path pattern `Foo/__init__` and `include_body=True`. If you don't know yet which methods in `Foo` you need to read or edit, - you can use `find_symbol` with name path pattern `Foo`, `include_body=False` and `depth=1` to get all (top-level) methods of `Foo` before proceeding + use `{{ tool_names['find_symbol'] }}` with name path pattern `Foo/__init__` and `include_body=True`. If you don't know yet which methods in `Foo` you need to read or edit, + you can use `{{ tool_names['find_symbol'] }}` with name path pattern `Foo`, `include_body=False` and `depth=1` to get all (top-level) methods of `Foo` before proceeding to read the desired methods with `include_body=True`. - You can understand relationships between symbols by using the `find_referencing_symbols` tool. + You can understand relationships between symbols by using the `{{ tool_names['find_referencing_symbols'] }}` tool. {% endif %} {% if 'read_memory' in available_tools -%}