FileWriteOrEdit
Write or edit files by specifying the percentage of changes needed, using either full content or search/replace blocks based on the modification scope.
Instructions
Writes or edits a file based on the percentage of changes.
Use absolute path only (~ allowed).
First write down percentage of lines that need to be replaced in the file (between 0-100) in percentage_to_change
percentage_to_change should be low if mostly new code is to be added. It should be high if a lot of things are to be replaced.
If percentage_to_change > 50, provide full file content in text_or_search_replace_blocks
If percentage_to_change <= 50, text_or_search_replace_blocks should be search/replace blocks.
Instructions for editing files.
Example
Input file
Edit format on the input file
SEARCH/REPLACE block Rules:
Every "<<<<<<< SEARCH" section must EXACTLY MATCH the existing file content, character for character, including all comments, docstrings, whitespaces, etc.
Including multiple unique SEARCH/REPLACE blocks if needed. Include enough and only enough lines in each SEARCH section to uniquely match each set of lines that need to change.
Keep SEARCH/REPLACE blocks concise. Break large SEARCH/REPLACE blocks into a series of smaller blocks that each change a small portion of the file. Include just the changing lines, and a few surrounding lines (0-3 lines) if needed for uniqueness. Other than for uniqueness, avoid including those lines which do not change in search (and replace) blocks. Target 0-3 non trivial extra lines per block.
Preserve leading spaces and indentations in both SEARCH and REPLACE blocks.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| file_path | Yes | #1: absolute file path | |
| percentage_to_change | Yes | #2: predict this percentage, calculated as number of existing lines that will have some diff divided by total existing lines. | |
| text_or_search_replace_blocks | Yes | #3: content/edit blocks. Must be after #2 in the tool xml | |
| thread_id | Yes | #4: thread_id |
Implementation Reference
- src/wcgw/client/tools.py:842-902 (handler)Main handler function for the FileWriteOrEdit tool. Determines whether to perform a full file write or a targeted edit based on the predicted percentage_to_change, delegating to write_file or do_diff_edit accordingly.def file_writing( file_writing_args: FileWriteOrEdit, coding_max_tokens: Optional[int], noncoding_max_tokens: Optional[int], context: Context, ) -> tuple[ str, dict[str, list[tuple[int, int]]] ]: # Updated to return message and file paths with line ranges """ Write or edit a file based on percentage of changes. If percentage_changed > 50%, treat content as direct file content. Otherwise, treat content as search/replace blocks. """ # Check if the thread_id matches current if file_writing_args.thread_id != context.bash_state.current_thread_id: # Try to load state from the thread_id if not context.bash_state.load_state_from_thread_id( file_writing_args.thread_id ): return ( f"Error: No saved bash state found for thread_id `{file_writing_args.thread_id}`. Please re-initialize to get a new id or use correct id.", {}, ) # Expand the path before checking if it's absolute path_ = expand_user(file_writing_args.file_path) if not os.path.isabs(path_): return ( f"Failure: file_path should be absolute path, current working directory is {context.bash_state.cwd}", {}, # Return empty dict instead of empty list for type consistency ) # If file doesn't exist, always use direct file_content mode content = file_writing_args.text_or_search_replace_blocks if not _is_edit(content, file_writing_args.percentage_to_change): # Use direct content mode (same as WriteIfEmpty) result, paths = write_file( WriteIfEmpty( file_path=path_, file_content=file_writing_args.text_or_search_replace_blocks, ), True, coding_max_tokens, noncoding_max_tokens, context, ) return result, paths else: # File exists and percentage <= 50, use search/replace mode result, paths = do_diff_edit( FileEdit( file_path=path_, file_edit_using_search_replace_blocks=file_writing_args.text_or_search_replace_blocks, ), coding_max_tokens, noncoding_max_tokens, context, ) return result, paths
- src/wcgw/types_.py:288-302 (schema)Pydantic BaseModel defining the input schema for FileWriteOrEdit, including fields for file_path, percentage_to_change, text_or_search_replace_blocks, and thread_id.class FileWriteOrEdit(BaseModel): # Naming should be in sorted order otherwise it gets changed in LLM backend. file_path: str = Field(description="#1: absolute file path") percentage_to_change: int = Field( description="#2: predict this percentage, calculated as number of existing lines that will have some diff divided by total existing lines." ) text_or_search_replace_blocks: str = Field( description="#3: content/edit blocks. Must be after #2 in the tool xml" ) thread_id: str = Field(description="#4: thread_id") def model_post_init(self, __context: Any) -> None: self.thread_id = normalize_thread_id(self.thread_id) return super().model_post_init(__context)
- src/wcgw/client/tool_prompts.py:74-88 (registration)MCP tool registration for FileWriteOrEdit, providing the JSON schema, name, detailed description, and annotations indicating it's destructive but idempotent.inputSchema=remove_titles_from_schema(FileWriteOrEdit.model_json_schema()), name="FileWriteOrEdit", description=""" - Writes or edits a file based on the percentage of changes. - Use absolute path only (~ allowed). - First write down percentage of lines that need to be replaced in the file (between 0-100) in percentage_to_change - percentage_to_change should be low if mostly new code is to be added. It should be high if a lot of things are to be replaced. - If percentage_to_change > 50, provide full file content in text_or_search_replace_blocks - If percentage_to_change <= 50, text_or_search_replace_blocks should be search/replace blocks. """ + diffinstructions, annotations=ToolAnnotations( destructiveHint=True, idempotentHint=True, openWorldHint=False ), ),
- src/wcgw/client/tools.py:1003-1015 (handler)Dispatch logic within get_tool_output that invokes the file_writing handler specifically for FileWriteOrEdit tool calls and handles the output and file tracking.elif isinstance(arg, FileWriteOrEdit): context.console.print("Calling file writing tool") result, write_edit_paths = file_writing( arg, coding_max_tokens, noncoding_max_tokens, context ) output = result, 0.0 # Add write/edit paths with their ranges to our tracking dictionary for path, ranges in write_edit_paths.items(): if path in file_paths_with_ranges: file_paths_with_ranges[path].extend(ranges) else: file_paths_with_ranges[path] = ranges.copy()
- src/wcgw/client/mcp_server/server.py:95-148 (registration)MCP server handler for all tool calls, including FileWriteOrEdit. Uses which_tool_name and parse_tool_by_name to instantiate the tool and delegates to get_tool_output.async def handle_call_tool( name: str, arguments: dict[str, Any] | None ) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]: global BASH_STATE if not arguments: raise ValueError("Missing arguments") tool_type = which_tool_name(name) tool_call = parse_tool_by_name(name, arguments) try: assert BASH_STATE output_or_dones, _ = get_tool_output( Context(BASH_STATE, BASH_STATE.console), tool_call, default_enc, 0.0, lambda x, y: ("", 0), 24000, # coding_max_tokens 8000, # noncoding_max_tokens ) except Exception as e: output_or_dones = [f"GOT EXCEPTION while calling tool. Error: {e}"] content: list[types.TextContent | types.ImageContent | types.EmbeddedResource] = [] for output_or_done in output_or_dones: if isinstance(output_or_done, str): if issubclass(tool_type, Initialize): # Prepare the original hardcoded message original_message = """ - Additional important note: as soon as you encounter "The user has chosen to disallow the tool call.", immediately stop doing everything and ask user for the reason. Initialize call done. """ # If custom instructions exist, prepend them to the original message if CUSTOM_INSTRUCTIONS: output_or_done += f"\n{CUSTOM_INSTRUCTIONS}\n{original_message}" else: output_or_done += original_message content.append(types.TextContent(type="text", text=output_or_done)) else: content.append( types.ImageContent( type="image", data=output_or_done.data, mimeType=output_or_done.media_type, ) ) return content