(execCtx ExecutionContext, input any)
| 164 | } |
| 165 | |
| 166 | func (t *EditFileTool) ValidateInput(execCtx ExecutionContext, input any) (bool, string) { |
| 167 | in := deref[EditFileInput](input) |
| 168 | path := resolveToolPath(execCtx, in.FilePath) |
| 169 | if ok, msg := checkAllowedWriteRoots(path, execCtx["allowed_write_roots"]); !ok { |
| 170 | return false, formatEditError(editFileNotFound, msg) |
| 171 | } |
| 172 | if in.OldString == in.NewString { |
| 173 | return false, formatEditError(editNoOp, "old_string and new_string must be different.") |
| 174 | } |
| 175 | info, err := os.Stat(path) |
| 176 | if err != nil { |
| 177 | hint := "" |
| 178 | if !filepath.IsAbs(in.FilePath) { |
| 179 | hint = " (path is relative — use absolute paths)" |
| 180 | } |
| 181 | return false, formatEditError(editFileNotFound, fmt.Sprintf("File not found: %s%s", in.FilePath, hint)) |
| 182 | } |
| 183 | if !info.Mode().IsRegular() { |
| 184 | return false, formatEditError(editFileNotFound, fmt.Sprintf("Not a file: %s", in.FilePath)) |
| 185 | } |
| 186 | if strings.EqualFold(filepath.Ext(path), ".ipynb") { |
| 187 | return false, formatEditError(editNotebookRedirect, fmt.Sprintf("Notebook files must use notebook_edit tool: %s", in.FilePath)) |
| 188 | } |
| 189 | if in.OldString != "" && !truthy(execCtx["skip_read_before_edit"]) { |
| 190 | owner, _ := execCtx["parent_state"].(readFileStateOwner) |
| 191 | if owner == nil { |
| 192 | return false, formatEditError(editUnreadFile, "File has not been read yet. Read it first before editing.") |
| 193 | } |
| 194 | fileState, ok := owner.GetReadFileState(path) |
| 195 | if !ok { |
| 196 | fileState, ok = owner.GetReadFileState(in.FilePath) |
| 197 | } |
| 198 | if !ok { |
| 199 | return false, formatEditError(editUnreadFile, "File has not been read yet. Read it first before editing.") |
| 200 | } |
| 201 | if fileState.IsPartialView { |
| 202 | return false, formatEditError(editUnreadFile, "File was only partially read. Re-read the full file before editing.") |
| 203 | } |
| 204 | currentMTime := fileMTime(path) |
| 205 | if currentMTime > fileState.TimeStamp { |
| 206 | if runtime.GOOS == "windows" { |
| 207 | data, err := os.ReadFile(path) |
| 208 | if err != nil { |
| 209 | return false, formatEditError(editStaleRead, "File has been modified since it was last read. Re-read the file before editing.") |
| 210 | } |
| 211 | currentContent := strings.ReplaceAll(decodeToolBytes(data), "\r\n", "\n") |
| 212 | cachedContent := strings.ReplaceAll(fileState.Content, "\r\n", "\n") |
| 213 | if currentContent == cachedContent { |
| 214 | fileState.TimeStamp = currentMTime |
| 215 | owner.SetReadFileState(path, fileState) |
| 216 | } else { |
| 217 | return false, formatEditError(editStaleRead, "File has been modified since it was last read. Re-read the file before editing.") |
| 218 | } |
| 219 | } else { |
| 220 | return false, formatEditError(editStaleRead, "File has been modified since it was last read. Re-read the file before editing.") |
| 221 | } |
| 222 | } |
| 223 | } |
nothing calls this directly
no test coverage detected