Place the REPL's facade listing in the tool description or the activation message

* single-project sessions: in the REPL tool's description (fixed at registration)
* otherwise: in the project activation message (facades depend on the activated project)
* REPL tool description wording adjusted (listing location, notebook semantics)

Peripheral changes:
* is_tool_function_available: in REPL mode, active tools without facade counterparts count as available
This commit is contained in:
Dominik Jain authored and Dominik Jain committed 2026-09-15 12:50:44 +02:00
1 parent d76dc3d870
commit 4b9ce9ad44
4 files changed
+32 -11

No files matched your search

+9 -3
View File
@@ -1251,10 +1251,15 @@ class SerenaAgent:
msg += self._render_prompt(mode.prompt, tag="mode", tag_name_attr=mode.name) + "\n"
self._project_prompt_status.mark_mode_prompts_as_provided(session_id)
# add project-specific prompt
# add the project's prompt (if any)
if proj.project_config.initial_prompt:
msg += "\n" + self._render_prompt(proj.project_config.initial_prompt, tag="project-instructions")
# when the REPL is active, add information on available facades if the agent is not in single-project mode
# (for single-project mode where the facades can't change, they are provided in the tool's description)
if self._active_tools.contains_tool_class(SerenaReplTool) and not self.is_single_project():
msg += f"\n\nAvailable facades for the `{SerenaReplTool.get_name_from_cls()}` tool:\n" + self.get_repl().entrypoint.overview()
self._project_prompt_status.mark_project_activation_message_as_provided(session_id)
return msg
@@ -1571,10 +1576,11 @@ class SerenaAgent:
:param tool_class: the tool class
:return: whether the function is available
"""
is_active_tool = self._active_tools.contains_tool_class(tool_class)
if self._agent_interface == AgentInterface.TOOLS:
return self._active_tools.contains_tool_class(tool_class)
return is_active_tool
elif self._agent_interface == AgentInterface.REPL:
return self.get_repl().entrypoint.is_tool_function_available(tool_class)
return is_active_tool or self.get_repl().entrypoint.is_tool_function_available(tool_class)
else:
raise NotImplementedError
+12 -8
View File
@@ -13,25 +13,29 @@ class SerenaReplTool(Tool, ToolMarkerOptional, ToolMarkerBeta):
"""
def get_apply_docstring(self) -> str:
return self.get_apply_docstring_from_cls() + "\n\nAvailable facades:\n" + self.agent.get_repl().entrypoint.overview()
docs = self.get_apply_docstring_from_cls()
if self.agent.is_single_project():
docs += "\n\nAvailable facades:\n" + self.agent.get_repl().entrypoint.overview()
else:
docs += "\n\nAvailable facades are provided at project activation"
return docs
def apply(self, session: str, code: str) -> str:
"""
Executes the given Python code, which has access to Serena's functionality through the object `s`.
The functionality is organised in facades, which are attributes of `s` (e.g. `s.myfacade`); the available
facades and their methods are listed below.
The functionality is organised in facades, which are attributes of `s` (e.g. `s.myfacade`).
Documentation: Use `s.info("<facade>")` when you will use a facade's functionality (it documents all common
operations at once) and `s.info("<facade>.<method>")` for a single or a rarely needed operation. Several items
can be requested in one call, e.g. `s.info("lsp", "edit.replace_content")`.
`s.info("<facade>")` documents the facade's operations only, not their result types. Result types are given
in the method listing below (`method -> Type`); request their documentation via `s.info("<Type>")`, which
`s.info("<facade>")` documents the facade's operations only, not their result types. The facade listing
provides result types (`method -> Type`); request their documentation via `s.info("<Type>")`, which
includes the types they contain, ONLY if you intend to process results in code (filter, aggregate, chain
calls). If you simply want the result, make it the last expression: results are rendered for you.
calls).
The code is executed like a notebook cell: if its last statement is an expression, the expression's value is
the result (do not use `return`). Results are rendered in a form suitable for you; lists are rendered
element-wise, strings are passed through unchanged.
the result. Results are rendered in a form suitable for you; lists are rendered element-wise,
strings are passed through unchanged.
Output size: methods with a `max_answer_chars` parameter limit the size of the rendered result (-1 uses the
configured default). If the limit is exceeded, a shortened result (or no content) is rendered instead;
+4
View File
@@ -29,6 +29,10 @@ class MockAgent:
def get_repl() -> SerenaRepl:
return SerenaRepl([], ApiScope())
@staticmethod
def is_single_project() -> bool:
return False
class BaseMockTool(Tool):
"""A mock Tool class for testing."""
+7
View File
@@ -919,6 +919,13 @@ class TestSerenaAgent:
assert exposed == expected
assert "s.lsp" in agent.get_tool(SerenaReplTool).apply(agent.create_session().session_id, "s.info()")
# the facade listing is part of the (fixed) tool description in single-project sessions,
# and of the activation message otherwise (where the facades depend on the activated project)
tool_description = agent.get_tool(SerenaReplTool).get_apply_docstring()
activation_message = agent.get_project_activation_message("test_session")
assert ("s.lsp:" in tool_description) == context.single_project
assert ("s.lsp:" in activation_message) == (not context.single_project)
# prompts refer to operations by their qualified REPL names, e.g. `lsp.find_symbol` instead of the tool name
system_prompt = agent.create_system_prompt()
assert "`lsp.find_symbol`" in system_prompt