Source code for atloop.tools.filesystem.append_file
"""Append 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 AppendFileTool(BaseTool):
"""
Tool for appending content to the end of existing files.
**⚠️ This tool is fully available and can be used normally!**
**Use cases:**
- Continuing to write files that exceed 6,000 characters (after initial write_file)
- Adding log entries
- Appending comments or notes
- Building files incrementally
**Key differences from write_file and edit_file:**
- `write_file`: Completely overwrites file (creates or replaces entire file)
- `edit_file`: Replaces specific text within file (precise modifications)
- `append_file`: Adds content to end of file (doesn't modify existing content)
**Content handling:**
- Content is appended exactly as provided
- Preserves trailing newlines in input content
- Unlike write_file/edit_file, doesn't normalize trailing newlines
"""
[docs]
def __init__(self, sandbox: SandboxAdapter):
"""
Initialize append file tool.
Args:
sandbox: Sandbox adapter instance
"""
self.sandbox = sandbox
@property
def name(self) -> str:
"""Tool name."""
return "append_file"
@property
def description(self) -> str:
"""Tool description."""
return "追加内容到文件(在现有文件末尾添加内容)"
[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 append file tool.
**⚠️ IMPORTANT**: This tool is fully available and can be used normally!
Don't assume it's unavailable - it works perfectly fine.
**Args:**
args: Tool arguments dictionary
- path (str, required): File path. Relative paths are relative to /workspace.
File must exist (this tool appends to existing files, doesn't create new ones).
- content (str, required): Content to append to the end of the file.
**Returns:**
ToolResult with:
- ok (bool): True if content was appended 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:**
# Continue writing a long document (after initial write_file with 6k chars)
append_file(
path="long_document.md",
content="\\n\\n## Chapter 2\\nThis is the continuation..."
)
# Add a log entry
append_file(
path="app.log",
content="[2024-01-01] User logged in\\n"
)
**Content Appending Behavior:**
- Content is appended **exactly as provided**, without modification
- If content ends with '\\n', it's appended with the newline
- If content doesn't end with '\\n', it's appended without a newline
- This differs from `write_file` and `edit_file`, which normalize trailing newlines
- Use this when you need precise control over trailing newlines
**Common Workflow:**
1. First turn: Use `write_file` to create file with first 6k characters
2. Subsequent turns: Use `append_file` to continue adding content
3. Check file: Use `read_file` to verify complete content
**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)
content_escaped = shlex.quote(content)
# Use printf to append content exactly as provided (no extra newline)
# printf '%s' ensures exact content without heredoc's trailing newline
cmd = f"printf '%s' {content_escaped} >> {path_escaped}"
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)},
)