-
Notifications
You must be signed in to change notification settings - Fork 3.3k
Expand file tree
/
Copy pathwrite_files.py
More file actions
183 lines (154 loc) · 5.94 KB
/
write_files.py
File metadata and controls
183 lines (154 loc) · 5.94 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""File writing tool for Agent Builder Assistant."""
from __future__ import annotations
from datetime import datetime
from pathlib import Path
import shutil
from typing import Any
from typing import Dict
from typing import List
from typing import Optional
from google.adk.tools.tool_context import ToolContext
from ..utils.resolve_root_directory import resolve_file_path
async def write_files(
files: Dict[str, str],
tool_context: ToolContext,
create_backup: bool = False,
create_directories: bool = True,
) -> Dict[str, Any]:
"""Write content to multiple files with optional backup creation.
This tool writes content to multiple files. It's designed for creating
Python tools, callbacks, configuration files, and other code files.
Args:
files: Dict mapping file_path to content to write
create_backup: Whether to create backups of existing files (default: False)
create_directories: Whether to create parent directories (default: True)
Returns:
Dict containing write operation results:
- success: bool indicating if all writes succeeded
- files: dict mapping file_path to file info:
- file_size: size of written file in bytes
- existed_before: bool indicating if file existed before write
- backup_created: bool indicating if backup was created
- backup_path: path to backup file if created
- error: error message if write failed for this file
- successful_writes: number of files written successfully
- total_files: total number of files requested
- errors: list of general error messages
"""
try:
# Get session state for path resolution
session_state = tool_context._invocation_context.session.state
project_root: Optional[Path] = None
if session_state is not None:
try:
project_root = resolve_file_path(".", session_state).resolve()
except Exception:
project_root = None
result = {
"success": True,
"files": {},
"successful_writes": 0,
"total_files": len(files),
"errors": [],
}
for file_path, content in files.items():
# Resolve file path using session state
resolved_path = resolve_file_path(file_path, session_state)
file_path_obj = resolved_path.resolve()
file_info = {
"file_size": 0,
"existed_before": False,
"backup_created": False,
"backup_path": None,
"error": None,
"package_inits_created": [],
}
try:
# Check if file already exists
file_info["existed_before"] = file_path_obj.exists()
# Create parent directories if needed
if create_directories:
file_path_obj.parent.mkdir(parents=True, exist_ok=True)
if file_path_obj.suffix == ".py" and project_root is not None:
created_inits = _ensure_package_inits(file_path_obj, project_root)
if created_inits:
file_info["package_inits_created"] = created_inits
# Create backup if requested and file exists
if create_backup and file_info["existed_before"]:
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
backup_path = file_path_obj.with_suffix(
f".backup_{timestamp}{file_path_obj.suffix}"
)
try:
shutil.copy2(file_path_obj, backup_path)
file_info["backup_created"] = True
file_info["backup_path"] = str(backup_path)
except Exception as e:
file_info["error"] = f"Failed to create backup: {str(e)}"
result["success"] = False
result["files"][str(file_path_obj)] = file_info
continue
# Write content to file
with open(file_path_obj, "w", encoding="utf-8") as f:
f.write(content)
# Verify write and get file size
if file_path_obj.exists():
file_info["file_size"] = file_path_obj.stat().st_size
result["successful_writes"] += 1
else:
file_info["error"] = "File was not created successfully"
result["success"] = False
except Exception as e:
file_info["error"] = f"Write failed: {str(e)}"
result["success"] = False
result["files"][str(file_path_obj)] = file_info
return result
except Exception as e:
return {
"success": False,
"files": {},
"successful_writes": 0,
"total_files": len(files) if files else 0,
"errors": [f"Write operation failed: {str(e)}"],
}
def _ensure_package_inits(
file_path: Path,
project_root: Path,
) -> List[str]:
"""Ensure __init__.py files exist for importable subpackages (not project root)."""
created_inits: List[str] = []
try:
target_parent = file_path.parent.resolve()
root_path = project_root.resolve()
relative_parent = target_parent.relative_to(root_path)
except Exception:
return created_inits
def _touch_init(directory: Path) -> None:
init_file = directory / "__init__.py"
if not init_file.exists():
init_file.touch()
created_inits.append(str(init_file))
root_path.mkdir(parents=True, exist_ok=True)
if not relative_parent.parts:
return created_inits
current_path = root_path
for part in relative_parent.parts:
if part in (".", ""):
continue
current_path = current_path / part
current_path.mkdir(parents=True, exist_ok=True)
_touch_init(current_path)
return created_inits