Improve write_file fallback handling
This commit is contained in:
+51
-9
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user