Packages
fnord
0.8.27
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 individual files 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(%{"instructions" => instructions}) do
{"Planning and implementing changes", instructions}
end
@impl AI.Tools
def ui_note_on_result(%{"instructions" => instructions}, result) do
{"Changes applied:",
"""
# 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: """
Launches a collection of AI Agents to plan and implement code changes within the project.
For the best results, provide clear, meticulously detailed instructions for the coding 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; the agents have NO access to the prior conversation; they ONLY know what YOU tell them.
If your instructions are ambiguous or unclear, this tool may not be able to generate a valid plan or implement the changes correctly.
""",
parameters: %{
type: "object",
required: ["instructions"],
properties: %{
instructions: %{
type: "string",
description: """
Clear, detailed instructions for the coding task you wish to perform.
A good plan will include:
- A clear explanation of the purpose and intent of the changes
- A clear description of the changes to be made
- All relevant context required to implement the changes as intended
- The scope of the changes, including any dependencies on other files or functions
- Any conventions, style guides, or patterns that should be followed
"""
}
}
}
}
}
end
@impl AI.Tools
def call(%{"instructions" => instructions}) do
instructions
|> code_stuff()
|> case do
{:ok, result} ->
UI.info("Coding completed", result)
{:ok, result}
{:error, reason} ->
UI.error("Coding failed", reason)
reason =
reason
|> String.split("\n")
|> Enum.map(&"> #{&1}")
|> Enum.join("\n")
{:error,
"""
FAILURE: The coder_tool was unable to apply the requested changes.
No changes were made to the file.
#{reason}
**IMPORTANT:** If you are unable to resolve this issue with improved
instructions, you MUST ensure that you notify the user of the failure
and provide detailed instructions on how to implement the changes
manually.
"""}
end
end
defp code_stuff(instructions) do
with {:ok, list_id} <- plan_steps(instructions),
:ok <- do_steps(list_id) do
{:ok,
list_id
|> TaskServer.get_list()
|> TaskServer.as_string(true)}
end
end
defp plan_steps(instructions) do
UI.info("Planning changes")
with {:ok, list_id} <- AI.Agent.Coder.Planner.get_response(%{instructions: instructions}) do
UI.info("Planning completed", TaskServer.as_string(list_id))
{:ok, list_id}
end
end
defp do_steps(list_id) do
list_id
|> TaskServer.get_list()
|> do_steps(list_id)
end
defp do_steps(steps, list_id, acc \\ :ok)
defp do_steps(remaining_steps, list_id, {:error, label, reason}) do
msg = """
The coding task failed at step: #{label}
#{reason}
"""
# Fail the remaining tasks in the list
remaining_steps
|> Enum.each(fn %{id: id} ->
TaskServer.fail_task(list_id, id, "Cancelled due to failure at prior step: #{label}")
end)
{:error, msg}
end
defp do_steps([], _list_id, :ok), do: :ok
defp do_steps([step | steps], list_id, :ok) do
%{
"label" => label,
"file" => file,
"instructions" => instructions
} = step.data
with {:ok, result} <- do_step(label, file, instructions) do
UI.info("Step completed", label)
TaskServer.complete_task(list_id, step.id, result)
UI.info("Current task list", TaskServer.get_list(list_id) |> TaskServer.as_string(true))
do_steps(steps, list_id, :ok)
else
{:error, reason} ->
UI.warn("Step failed", label)
TaskServer.fail_task(list_id, step.id, inspect(reason))
do_steps(steps, list_id, {:error, label, reason})
end
end
defp do_step(label, file, instructions) do
UI.info("Performing coding task", label)
with {: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),
{:ok, result} <- apply_changes(file, start_line, end_line, replacement) do
UI.info(
"Coding task complete",
"""
# #{label}
Changes applied to #{file}:#{start_line}-#{end_line}.
# Outcome
#{result}
"""
)
{:ok, result}
end
end
defp identify_range(file, instructions) do
AI.Agent.Coder.RangeFinder.get_response(%{instructions: instructions, file: file})
|> 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} ->
{: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