Improve write_file fallback handling

This commit is contained in:
wuyang6
2026-05-12 19:52:41 +08:00
parent a48c86501a
commit 64787498dd
4 changed files with 93 additions and 21 deletions
+51 -9
View File
@@ -1,5 +1,6 @@
from __future__ import annotations
import base64
import hashlib
import json
import os
@@ -1824,23 +1825,34 @@ def _write_file(arguments: dict[str, Any], context: ToolExecutionContext) -> str
_ensure_write_allowed(context)
target = _resolve_path(_require_string(arguments, 'path'), context)
_ensure_not_platform_code_write(target, context)
content = arguments.get('content')
if not isinstance(content, str):
raise ToolExecutionError('content must be a string')
content = _coerce_write_file_content(arguments)
append = arguments.get('append', False)
if not isinstance(append, bool):
raise ToolExecutionError('append must be a boolean')
newline_at_end = arguments.get('newline_at_end', False)
if not isinstance(newline_at_end, bool):
raise ToolExecutionError('newline_at_end must be a boolean')
if newline_at_end and content and not content.endswith('\n'):
content += '\n'
previous_text: str | None = None
previous_sha256: str | None = None
if target.exists() and target.is_file():
previous_text = target.read_text(encoding='utf-8', errors='replace')
previous_sha256 = hashlib.sha256(previous_text.encode('utf-8')).hexdigest()
elif target.exists():
raise ToolExecutionError(f'Path exists and is not a file: {target}')
target.parent.mkdir(parents=True, exist_ok=True)
target.write_text(content, encoding='utf-8')
final_text = (previous_text or '') + content if append else content
target.write_text(final_text, encoding='utf-8')
rel = _display_path(target, context)
new_sha256 = hashlib.sha256(content.encode('utf-8')).hexdigest()
new_sha256 = hashlib.sha256(final_text.encode('utf-8')).hexdigest()
action = 'append_file' if append else 'write_file'
return (
f'wrote {rel} ({len(content)} chars)',
f'{"appended" if append else "wrote"} {rel} ({len(content)} chars)',
{
'action': 'write_file',
'action': action,
'path': rel,
'append': append,
'before_exists': previous_text is not None,
'before_sha256': previous_sha256,
'before_size': len(previous_text) if previous_text is not None else 0,
@@ -1850,13 +1862,43 @@ def _write_file(arguments: dict[str, Any], context: ToolExecutionContext) -> str
else None
),
'after_sha256': new_sha256,
'after_size': len(content),
'after_preview': _snapshot_text(content),
'after_size': len(final_text),
'after_preview': _snapshot_text(final_text),
'content_length': len(content),
},
)
def _coerce_write_file_content(arguments: dict[str, Any]) -> str:
provided = [
key
for key in ('content', 'content_lines', 'content_base64')
if key in arguments and arguments[key] is not None
]
if len(provided) != 1:
raise ToolExecutionError(
'Provide exactly one of content, content_lines, or content_base64'
)
source = provided[0]
if source == 'content':
content = arguments['content']
if not isinstance(content, str):
raise ToolExecutionError('content must be a string')
return content
if source == 'content_lines':
content_lines = arguments['content_lines']
if not isinstance(content_lines, list) or not all(isinstance(line, str) for line in content_lines):
raise ToolExecutionError('content_lines must be an array of strings')
return '\n'.join(content_lines)
encoded = arguments['content_base64']
if not isinstance(encoded, str):
raise ToolExecutionError('content_base64 must be a string')
try:
return base64.b64decode(encoded, validate=True).decode('utf-8')
except (ValueError, UnicodeDecodeError) as exc:
raise ToolExecutionError('content_base64 must be valid base64-encoded UTF-8 text') from exc
def _edit_file(arguments: dict[str, Any], context: ToolExecutionContext) -> str:
_ensure_write_allowed(context)
target = _resolve_path(_require_string(arguments, 'path'), context, allow_missing=False)