Packages
fnord
0.8.26
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/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.
If the replacement is longer or shorter than the original, that's OK; it will still replace the entire region.
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.
Always try to match the style, formatting, and conventions of the original code.
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
lines =
file
|> File.read!()
|> Util.numbered_lines()
|> String.split("\n")
before_hunk =
lines
|> Enum.take(start_line - 1)
|> Enum.join("\n")
after_hunk =
lines
|> Enum.drop(end_line)
|> Enum.join("\n")
hunk =
lines
|> Enum.slice(start_line - 1, end_line - start_line + 1)
|> Enum.join("\n")
file_contents_with_marker =
"""
#{before_hunk}
<!-- REPLACE THIS ENTIRE HUNK -->
#{hunk}
<!-- END OF HUNK -->
#{after_hunk}
"""
user = """
# File: #{file}
```
#{file_contents_with_marker}
```
# 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.
Respond ONLY with the raw file content for the specified range of lines.
#{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}
```
#{preview}
```
""")
{: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