Source code for atloop.tools.filesystem.write_file

"""Write file tool."""

import shlex
from typing import Any, Dict, Optional

from atloop.runtime.sandbox_adapter import SandboxAdapter
from atloop.tools.base import BaseTool, ToolResult


[docs] class WriteFileTool(BaseTool): """ Tool for writing files (creates new files or completely overwrites existing files). **⚠️ Important Usage Guidelines:** - **Use for**: Creating new files or completely rewriting existing files - **Do NOT use for**: Modifying parts of existing files (use `edit_file` instead) - **Character limit**: Maximum 6,000 characters per turn - **Directory creation**: Automatically creates parent directories if they don't exist **When to use write_file vs edit_file:** - ✅ Creating a new file → use `write_file` - ✅ Completely rewriting a file → use `write_file` - ❌ Modifying a function/class → use `edit_file` (more precise and safer) - ❌ Adding/removing a few lines → use `edit_file` (more precise and safer) **File content handling:** - Uses placeholder mechanism: content should be `FILE_CONTENT_#1`, `FILE_CONTENT_#2`, etc. - Actual content follows the JSON output, delimited by `---(FILE_CONTENT_#1)---` - This prevents JSON parsing issues with large content **Trailing newline behavior:** - Files always end with exactly one newline character - Input content's trailing newline is normalized automatically """
[docs] def __init__(self, sandbox: SandboxAdapter): """ Initialize write file tool. Args: sandbox: Sandbox adapter instance """ self.sandbox = sandbox
@property def name(self) -> str: """Tool name.""" return "write_file" @property def description(self) -> str: """Tool description.""" return "写入文件(完全覆盖整个文件内容。如果要修改文件的部分内容,请使用 edit_file;如果要追加内容,请使用 append_file)"
[docs] def validate_args(self, args: Dict[str, Any]) -> tuple[bool, Optional[str]]: """Validate arguments.""" if "path" not in args: return False, "Missing required argument: 'path'" if "content" not in args: return False, "Missing required argument: 'content'" if not isinstance(args["path"], str): return False, "Argument 'path' must be a string" if not isinstance(args["content"], str): return False, "Argument 'content' must be a string" return True, None
[docs] def execute(self, args: Dict[str, Any]) -> ToolResult: """ Execute write file tool. **⚠️ WARNING**: This tool completely overwrites the entire file. If the file exists, all existing content will be replaced with the new content. **When to use this tool:** - ✅ Creating a new file - ✅ Completely rewriting an existing file - ❌ Modifying part of a file → use `edit_file` instead - ❌ Appending content → use `append_file` instead **Args:** args: Tool arguments dictionary - path (str, required): File path. Relative paths are relative to /workspace. Parent directories are automatically created if they don't exist. - content (str, required): File content to write. This will completely replace any existing file content. Maximum 6,000 characters per turn. Use placeholder `FILE_CONTENT_#1` in JSON, actual content follows after. **Returns:** ToolResult with: - ok (bool): True if file was written successfully (no errors in stderr) - stdout (str): Command output (usually empty) - stderr (str): Error messages if any (empty string means success) - meta (dict): Contains path, cmd, duration_ms **Examples:** # Create a new Python file write_file(path="src/main.py", content="FILE_CONTENT_#1") # Then provide actual content after JSON: # ---(FILE_CONTENT_#1)--- # def main(): # print("Hello, World!") # ---(FILE_CONTENT_#1)--- **Note on Trailing Newlines:** This tool uses heredoc (cat > file <<'FILE_EOF') which automatically adds a trailing newline. The tool normalizes input to ensure files always end with exactly one newline: - If content ends with '\\n', it's removed before writing (heredoc adds one) - If content doesn't end with '\\n', heredoc adds one - Result: File always ends with exactly one newline character - Exception: Empty string content results in a file with one newline **Error Handling:** - Success is determined by stderr content, not exit code - If stderr is empty, the operation succeeded - Check stderr for specific error messages if ok=False """ path = args["path"] content = args["content"] # Handle paths - sandbox runs in /workspace directory # Relative paths are already relative to /workspace path_escaped = shlex.quote(path) # Ensure directory exists - create parent directories if needed import os dir_path = os.path.dirname(path) if dir_path: dir_path_escaped = shlex.quote(dir_path) mkdir_cmd = f"mkdir -p {dir_path_escaped}" mkdir_result = self.sandbox.exec_shell( command=mkdir_cmd, workdir="/workspace", timeout_seconds=10, ) # Check if directory creation had errors in stderr if mkdir_result.get("stderr", "").strip(): # Directory creation may have failed, but continue anyway # The write operation will fail if directory doesn't exist pass # Use heredoc to write file # Heredoc automatically adds a newline before FILE_EOF, so we need to handle trailing newlines # If content ends with \n, remove it to avoid double newline content_for_write = content if content_for_write.endswith("\n"): content_for_write = content_for_write[:-1] cmd = f"cat > {path_escaped} <<'FILE_EOF'\n{content_for_write}\nFILE_EOF" result = self.sandbox.exec_shell( command=cmd, workdir="/workspace", timeout_seconds=30, ) # Determine success based on stderr content, not exit code stderr = result.get("stderr", "") ok = not bool(stderr.strip()) # Success if no error messages in stderr return ToolResult( ok=ok, stdout=result.get("stdout", ""), stderr=stderr, meta={"path": path, "cmd": cmd, "duration_ms": result.get("durationMs", 0)}, )