Packages
fnord
0.8.19
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/coder.ex
defmodule AI.Tools.Coder do
@behaviour AI.Tools
@doc """
This tool relies on line numbers within the file to identify ranges. If those
numbers change between the time the range is identified and the time the
changes are applied, the tool will fail to apply the changes correctly.
"""
@impl AI.Tools
def async?, do: false
@impl AI.Tools
def is_available?, do: AI.Tools.has_indexed_project()
@impl AI.Tools
def ui_note_on_request(%{"file" => file, "instructions" => instructions}) do
{"Editing file #{file}", instructions}
end
@impl AI.Tools
def ui_note_on_result(%{"file" => file, "instructions" => instructions}, result) do
{"Changes applied to #{file}",
"""
# Instructions
#{instructions}
# Result
#{result}
"""}
end
@impl AI.Tools
def read_args(args), do: {:ok, args}
@impl AI.Tools
def spec() do
%{
type: "function",
function: %{
name: "coder_tool",
description: """
Triggers an LLM agent to perform a coding task to a contiguous region within a single file in the project source root.
The LLM has no access to the tool_calls you have available. It can ONLY edit files. YOU must provide all information and context required to perform the task.
Instructions should include details about conventions, style, preferred modules and patterns, and anything else you wish to have reflected in the output.
Instructions must include ALL relevant context; this agent has NO access to the prior conversation; they ONLY know what YOU tell them.
Instructions must include clear, unambiguous "anchors", identifying a *single* region of the file to edit.
Examples:
- "Add a new, private function at the end of the file (in a syntactically appropriate location) named `blarg`. The function accepts 2 positional arguments, ..."
- "In the import list at the top of the file, remove the import for `foo.bar` and add an import for `baz.qux`. Note that the user prefers never to use aliases."
- "This file contains a mix of spaces and tabs. Convert all tabs to spaces, and ensure the indentation is consistent with 2 spaces per level."
""",
parameters: %{
type: "object",
required: ["file", "instructions"],
properties: %{
file: %{
type: "string",
description: "The path to the file to edit, relative to the project source root."
},
instructions: %{
type: "string",
description: "Clear, detailed instructions for the changes to make to the file."
}
}
}
}
}
end
@impl AI.Tools
def call(args) do
with {:ok, project} <- Store.get_project(),
{:ok, file} <- AI.Tools.get_arg(args, "file"),
{:ok, instructions} <- AI.Tools.get_arg(args, "instructions"),
:ok <- validate_path(file, project.source_root),
{:ok, {start_line, end_line}} <- identify_range(file, instructions),
{:ok, replacement, preview} <- dry_run(file, instructions, start_line, end_line),
:ok <- confirm_changes(file, instructions, preview) do
{:ok, result} = apply_changes(file, start_line, end_line, replacement)
UI.info("Changes applied to #{file}:#{start_line}-#{end_line}", result)
{:ok, result}
else
{:error, :enoent} ->
{:error,
"""
FAILURE: The coder_tool was unable to apply the requested changes. No changes were made to the file.
The requested file does not exist or is not a regular file.
Please use the `list_files_tool` or one of the search tools to find the correct file path.
"""}
{:error, reason} ->
{:error,
"""
FAILURE: The coder_tool was unable to apply the requested changes. No changes were made to the file.
#{reason}
"""}
end
end
defp validate_path(path, root) do
cond do
!Util.path_within_root?(path, root) -> {:error, "not within project root"}
!File.exists?(path) -> {:error, :enoent}
!File.regular?(path) -> {:error, :enoent}
true -> :ok
end
end
defp identify_range(file, instructions) do
%{instructions: instructions, file: file}
|> AI.Agent.Coder.RangeFinder.get_response()
|> case do
{:ok, {start_line, end_line}} ->
UI.info("Hunk identified in #{file}", "Lines #{start_line}...#{end_line}")
{:ok, {start_line, end_line}}
{:identify_error, msg} ->
UI.warn("Coding failed", """
The agent was unable to identify a contiguous range of lines in the file based on the provided instructions.
#{msg}
""")
{:error,
"""
The agent was unable to identify a single, contiguous range of lines in the file based on the provided instructions:
#{msg}
"""}
other ->
other
end
end
defp dry_run(file, instructions, start_line, end_line) do
%{file: file, instructions: instructions, start_line: start_line, end_line: end_line}
|> AI.Agent.Coder.DryRun.get_response()
end
defp confirm_changes(file, instructions, preview) do
%{file: file, instructions: instructions, preview: preview}
|> AI.Agent.Coder.Reviewer.get_response()
|> case do
:ok ->
UI.info("Reviewer approved changes", file)
:ok
{:confirm_error, error} ->
UI.warn("Reviewer rejected changes to #{file}", error)
{:error,
"""
The code reviewing agent found an error in the requested change:
#{error}
"""}
other ->
other
end
end
defp apply_changes(file, start_line, end_line, replacement) do
AI.Tools.File.Edit.call(%{
"path" => file,
"start_line" => start_line,
"end_line" => end_line,
"replacement" => replacement,
"dry_run" => false,
"context_lines" => 5
})
end
end