Packages
fnord
0.8.32
0.9.40
0.9.39
0.9.38
0.9.37
0.9.36
0.9.35
0.9.34
0.9.33
0.9.32
0.9.31
0.9.30
0.9.29
0.9.28
0.9.27
0.9.26
0.9.25
0.9.24
0.9.23
0.9.22
0.9.21
0.9.20
0.9.19
0.9.18
0.9.17
0.9.16
0.9.15
0.9.14
0.9.13
0.9.12
0.9.11
0.9.10
0.9.9
0.9.8
0.9.7
0.9.6
0.9.5
0.9.4
0.9.3
0.9.2
0.9.1
0.9.0
0.8.99
0.8.98
0.8.97
0.8.96
0.8.95
0.8.94
0.8.93
0.8.92
0.8.91
0.8.90
0.8.89
0.8.88
0.8.87
0.8.86
0.8.85
0.8.84
0.8.83
0.8.82
0.8.81
0.8.80
0.8.79
0.8.78
0.8.77
0.8.76
0.8.75
0.8.74
0.8.73
0.8.72
0.8.71
0.8.70
0.8.69
0.8.68
0.8.67
0.8.66
0.8.65
0.8.64
0.8.63
0.8.62
0.8.61
0.8.60
0.8.59
0.8.58
0.8.57
0.8.56
0.8.55
0.8.54
0.8.53
0.8.52
0.8.51
0.8.50
0.8.49
0.8.48
0.8.47
0.8.46
0.8.45
0.8.44
0.8.43
0.8.42
0.8.41
0.8.40
0.8.39
0.8.38
0.8.37
0.8.36
0.8.35
0.8.34
0.8.33
0.8.32
0.8.31
0.8.30
0.8.29
0.8.27
0.8.26
0.8.25
0.8.24
0.8.23
0.8.22
0.8.21
0.8.20
0.8.19
0.8.18
0.8.17
0.8.16
0.8.15
0.8.14
0.8.13
0.8.12
0.8.11
0.8.1
0.8.0
0.7.24
0.7.23
0.7.22
0.7.21
0.7.20
0.7.19
0.7.18
0.7.17
0.7.16
0.7.15
0.7.14
0.7.13
0.7.12
0.7.11
0.7.10
0.7.9
0.7.8
0.7.7
0.7.6
0.7.5
0.7.3
0.7.2
0.7.1
0.7.0
0.6.9
0.6.8
0.6.7
0.6.6
0.6.5
0.6.4
0.6.3
0.6.1
0.6.0
0.5.9
0.5.8
0.5.7
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.44
0.4.43
0.4.42
0.4.41
0.4.40
0.4.39
0.4.38
0.4.37
0.4.36
0.4.35
0.4.34
0.4.33
0.4.32
0.4.30
0.4.29
0.4.28
0.4.27
0.4.26
0.4.25
0.4.24
0.4.23
0.4.22
0.4.21
0.4.20
0.4.19
0.4.18
0.4.17
0.4.16
0.4.15
0.4.14
0.4.13
0.4.12
0.4.11
0.4.10
0.4.9
0.4.8
0.4.7
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.0
0.2.0
0.1.0
AI code archaeology
Current section
Files
Jump to
Current section
Files
lib/ai/tools/file/edit.ex
defmodule AI.Tools.File.Edit do
@moduledoc """
String-based code editing tool that uses exact and fuzzy string matching
instead of line numbers. Handles whitespace normalization while preserving
original formatting and indentation.
"""
# ----------------------------------------------------------------------------
# Types
# ----------------------------------------------------------------------------
@type edit_result :: %{
file: binary,
backup_file: binary,
diff: binary
}
# ----------------------------------------------------------------------------
# Behaviour Implementation
# ----------------------------------------------------------------------------
@behaviour AI.Tools
@impl AI.Tools
def async?, do: false
@impl AI.Tools
def is_available?, do: true
@impl AI.Tools
def read_args(args), do: {:ok, args}
@impl AI.Tools
def ui_note_on_request(%{"file" => file}) do
{"Preparing changes", file}
end
@impl AI.Tools
def ui_note_on_result(%{"file" => file}, _result) do
{"Changes applied", file}
end
@impl AI.Tools
def spec do
%{
type: "function",
function: %{
name: "file_edit_tool",
description: """
Perform atomic, well-anchored edits to a single file.
This is the best tool for simple changes that do not require extensive
planning, coordination, or span many files.
Use for:
- One-off line or block replacements
- Clear, unambiguous, file-local changes
- Fast, low-risk operations
NOT for system-wide, architectural, or ambiguous edits. Escalate to
`coder_tool` for those!
NOTE: This tool will FAIL if the file does not exist. Use the
`shell_tool` to create the file first.
""",
parameters: %{
type: "object",
required: ["file", "changes"],
additionalProperties: false,
properties: %{
file: %{
type: "string",
description: "Path (relative to project root) of the file to edit."
},
changes: %{
type: "array",
items: %{
type: "object",
description: """
A list of changes to apply to the file.
Steps are ordered logically, with each building on the previous.
They will be applied in sequence.
""",
required: ["change"],
additionalProperties: false,
properties: %{
change: %{
type: "string",
description: """
Clear, specific instructions for the changes to make. The
instructions must be concise and unambiguous.
Clearly define the section(s) of the file to modify. Provide
unambiguous "anchors" that identify the exact location of the
change.
For example:
- "Immediately after the declaration of the `main` function, add the following code block: ..."
- "Replace the entire contents of the `calculate` function with: ..."
- "At the top of the file, insert the following imports, ensuring they are properly formatted and ordered: ..."
- "Add a new function at the end of the module named `blarg` with the following contents: ..."
"""
}
}
}
}
}
}
}
}
end
@spec call(map) :: {:ok, edit_result} | {:error, term}
@impl AI.Tools
def call(args) do
with {:ok, file} <- AI.Tools.get_arg(args, "file"),
{:ok, changes} <- read_changes(args),
{:ok, result} <- do_edits(file, changes) do
{:ok, result}
end
end
# ----------------------------------------------------------------------------
# Internals
# ----------------------------------------------------------------------------
defp do_edits(file, changes) do
try do
with {:ok, project} <- Store.get_project(),
absolute_file <- Store.Project.expand_path(file, project),
{:ok, contents} <- apply_changes(absolute_file, changes),
{:ok, diff, backup_file} <- stage_changes(absolute_file, contents) do
{:ok,
%{
file: file,
backup_file: backup_file,
diff: diff
}}
end
rescue
error ->
{:error,
"""
An error occurred while applying changes to the file, but it's not your fault.
This is an internal application error.
Please report it to the developers.
Error message:
#{Exception.message(error)}
Stack trace:
```
#{Exception.format_stacktrace(__STACKTRACE__)}
```
"""}
end
end
defp stage_changes(file, contents) do
Util.Temp.with_tmp(contents, fn temp ->
with {:ok, diff} <- build_diff(file, temp),
{:ok, :approved} <- confirm_edit(file, diff),
{:ok, backup_file} <- backup_file(file),
:ok <- commit_changes(file, temp) do
{:ok, diff, backup_file}
end
end)
end
defp apply_changes(file, changes) do
AI.Agent.Code.Patcher
|> AI.Agent.new()
|> AI.Agent.get_response(%{file: file, changes: changes})
end
defp build_diff(file, staged) do
System.cmd("diff", ["-u", "-L", "ORIGINAL", "-L", "MODIFIED", file, staged],
stderr_to_stdout: true
)
|> case do
{output, 1} -> {:ok, String.trim_trailing(output)}
{_, 0} -> {:error, "no changes were made to the file"}
{error, code} -> {:error, "diff exited #{code}: #{error}"}
end
end
@spec confirm_edit(binary, binary) :: {:ok, :approved} | {:error, term}
defp confirm_edit(file, diff) do
Services.Approvals.confirm({file, colorize_diff(diff)}, :edit)
end
@spec colorize_diff(binary) :: Owl.Data.t()
def colorize_diff(diff) do
diff
|> String.split("\n")
|> Enum.map(fn line ->
cond do
String.starts_with?(line, "+") -> Owl.Data.tag(line <> "\n", [:white, :green_background])
String.starts_with?(line, "-") -> Owl.Data.tag(line <> "\n", [:white, :red_background])
true -> line <> "\n"
end
end)
end
defp backup_file(file) do
Services.BackupFile.create_backup(file)
end
defp commit_changes(file, staged) do
File.cp(staged, file)
end
defp read_changes(opts) do
with {:ok, changes} <- AI.Tools.get_arg(opts, "changes") do
try do
changes
|> Enum.map(& &1["change"])
|> Enum.map(&String.trim/1)
|> then(&{:ok, &1})
rescue
_ in FunctionClauseError ->
{:error,
"""
Invalid changes format.
Expected a list of objects with a "change" key.
Each change is expected to be a string.
"""}
end
end
end
end