Packages

fnord

0.8.21

AI code archaeology

Current section

Files

Jump to
fnord lib ai agent coder dry_run.ex
Raw

lib/ai/agent/coder/dry_run.ex

defmodule AI.Agent.Coder.DryRun do
@max_attempts 3
@model AI.Model.reasoning(:high)
@prompt """
You are an AI coding assistant within a larger AI system.
The Coordinating Agent asks you to apply changes to a file based on the provided instructions for a coding task.
Your changes will COMPLETELY replace the hunk defined by the line range in the file.
Note: The file contents include 1-based line numbers at the start of each line, separated by a pipe (|).
IMPORTANT: Your response must NOT include any line numbers or their pipe (`|`) separators.
Only output the raw file content for this region.
If you include line numbers or `|`, that will break the file.
Respond with a block of code that will replace all other content within the hunk.
Do not include any other text, comments, explanations, or markdown fences in your response.
"""
# ----------------------------------------------------------------------------
# Behaviour Implementation
# ----------------------------------------------------------------------------
@behaviour AI.Agent
@impl AI.Agent
def get_response(opts) do
with {:ok, instructions} <- Map.fetch(opts, :instructions),
{:ok, file} <- Map.fetch(opts, :file),
{:ok, start_line} <- Map.fetch(opts, :start_line),
{:ok, end_line} <- Map.fetch(opts, :end_line) do
dry_run_changes(file, instructions, start_line, end_line)
else
:error -> {:error, "Incorrect arguments passed to AI.Agent.Coder.DryRun."}
end
end
# ----------------------------------------------------------------------------
# Internals
# ----------------------------------------------------------------------------
@spec dry_run_changes(binary, binary, integer, integer, integer) ::
{:ok, binary, binary}
| {:error, binary}
defp dry_run_changes(file, instructions, start_line, end_line, attempt \\ 1)
defp dry_run_changes(_, _, _, _, attempt) when attempt > @max_attempts do
{:error, "Maximum attempts reached while trying to apply changes."}
end
defp dry_run_changes(file, instructions, start_line, end_line, attempt) do
file_contents =
file
|> File.read!()
|> Util.numbered_lines()
hunk =
file_contents
|> String.split("\n")
|> Enum.slice(start_line - 1, end_line - start_line + 1)
|> Enum.join("\n")
user = """
# File: #{file}
```
#{file_contents}
```
# Hunk: #{start_line}...#{end_line}
This is the hunk from the file that will be replaced by your response.
```
#{hunk}
```
# Instructions
Apply the following changes to the specified range of lines in the file.
Make ONLY the changes explicitly requested by the Coordinating Agent.
Perform NO other edits.
#{instructions}
"""
AI.Completion.get(
model: @model,
messages: [
AI.Util.system_msg(@prompt),
AI.Util.user_msg(user)
]
)
|> case do
{:ok, %{response: replacement}} ->
# If the replacement is a single line, determine if it needs a trailing
# newline, or if it was probably intended to be a deletion.
replacement = normalize_single_line_replacement(replacement)
AI.Tools.File.Edit.call(%{
"path" => file,
"start_line" => start_line,
"end_line" => end_line,
"replacement" => replacement,
"dry_run" => true,
"context_lines" => 5
})
|> case do
{:ok, preview} ->
UI.info("Patch prepared", """
# #{file} | #{start_line}...#{end_line}
```
#{replacement}
```
""")
{:ok, replacement, preview}
{:error, _} ->
dry_run_changes(file, instructions, start_line, end_line, attempt + 1)
end
{:error, _} ->
dry_run_changes(file, instructions, start_line, end_line, attempt + 1)
end
end
# Heuristic determination of whether a single line replacement should have a
# trailing newline. Complicated by the fact that a single line replacement
# could be a deletion (which we guess is the case for an empty string) or
# just a fuck-up where it generated a single line patch but forgot the
# newline.
defp normalize_single_line_replacement(replacement) do
cond do
# Likely a *deletion* - no \n needed
replacement == "" -> ""
# Already has a trailing newline
String.ends_with?(replacement, "\n") -> replacement
# Single line without trailing newline - add one
true -> replacement <> "\n"
end
end
end