(
self,
working_dir: str,
command: str,
path: str | None = None,
file: str | None = None,
file_text: str | None = None,
view_range: list[int] | str | None = None,
old_str: str | None = None,
new_str: str | None = None,
insert_line: int | str | None = None,
)
| 130 | ) |
| 131 | ) |
| 132 | async def str_replace_editor( |
| 133 | self, |
| 134 | working_dir: str, |
| 135 | command: str, |
| 136 | path: str | None = None, |
| 137 | file: str | None = None, |
| 138 | file_text: str | None = None, |
| 139 | view_range: list[int] | str | None = None, |
| 140 | old_str: str | None = None, |
| 141 | new_str: str | None = None, |
| 142 | insert_line: int | str | None = None, |
| 143 | ) -> str: |
| 144 | from codewiki.src.be.agent_tools.str_replace_editor import EditTool |
| 145 | from codewiki.src.be.utils import validate_mermaid_diagrams |
| 146 | |
| 147 | # ``Literal`` annotations would be the cleanest way to constrain these, |
| 148 | # but ``from __future__ import annotations`` turns them into forward refs |
| 149 | # that FastMCP's pydantic schema rebuild cannot resolve. Validate at |
| 150 | # call time instead so a bogus working_dir (empty string, ``"."``, etc.) |
| 151 | # cannot silently route writes to the repo root. |
| 152 | if working_dir not in _VALID_WORKING_DIRS: |
| 153 | return ( |
| 154 | f"Error: invalid `working_dir`={working_dir!r}. " |
| 155 | f"Allowed values: {list(_VALID_WORKING_DIRS)}." |
| 156 | ) |
| 157 | if command not in _VALID_EDITOR_COMMANDS: |
| 158 | return ( |
| 159 | f"Error: invalid `command`={command!r}. " |
| 160 | f"Allowed values: {list(_VALID_EDITOR_COMMANDS)}." |
| 161 | ) |
| 162 | |
| 163 | if path is None and file is None: |
| 164 | return "Error: Either `path` or `file` parameter must be provided." |
| 165 | if path is None: |
| 166 | path = file |
| 167 | if command != "view" and working_dir == "repo": |
| 168 | return "The `view` command is the only allowed command when `working_dir` is `repo`." |
| 169 | |
| 170 | # Reject absolute paths: ``Path("/abs/base") / "/abs/other"`` resolves to |
| 171 | # ``/abs/other``, which would silently bypass ``working_dir`` and let the |
| 172 | # agent write outside the docs path. Force the agent to pass a path |
| 173 | # relative to the chosen working_dir. |
| 174 | if os.path.isabs(path): |
| 175 | return ( |
| 176 | f"Error: `path` must be relative to `working_dir` ({working_dir!r}), " |
| 177 | f"got absolute path {path!r}. Pass a relative path like " |
| 178 | f"'module_name.md' (resolved under absolute_docs_path when " |
| 179 | f"working_dir='docs')." |
| 180 | ) |
| 181 | |
| 182 | view_range = _coerce_json_arg(view_range) |
| 183 | insert_line = _coerce_json_arg(insert_line) |
| 184 | |
| 185 | edit_tool = EditTool(self._deps.registry, self._deps.absolute_docs_path) |
| 186 | |
| 187 | base_dir = ( |
| 188 | self._deps.absolute_docs_path |
| 189 | if working_dir == "docs" |
nothing calls this directly
no test coverage detected