Packages
fnord
0.8.25
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/shell.ex
defmodule AI.Tools.Shell do
@moduledoc """
A tool for executing shell commands.
Allows for the user to approve a command before it is executed, and provides
a mechanism to approve all future executions of the same command and
subcommands, regardless of arguments.
"""
@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
with {:ok, desc} <- AI.Tools.get_arg(args, "description"),
{:ok, cmd} <- AI.Tools.get_arg(args, "cmd") do
cond do
desc == "" -> {:error, "The description cannot be empty."}
cmd == "" || cmd == " " -> {:error, "The command cannot be empty."}
true -> {:ok, %{"description" => desc, "cmd" => cmd}}
end
end
end
@impl AI.Tools
def ui_note_on_request(%{"cmd" => cmd}), do: {"Shell", cmd}
@impl AI.Tools
def ui_note_on_result(%{"cmd" => cmd}, result) do
lines = String.split(result, ~r/\r\n|\n/)
{result_str, additional} =
if length(lines) > 10 do
{first_lines, _rest} = Enum.split(lines, 10)
remaining = length(lines) - 10
{
Enum.join(first_lines, "\n"),
UI.italicize("...plus #{remaining} additional lines")
}
else
{Enum.join(lines, "\n"), ""}
end
{"Shell",
"""
$ #{cmd}
#{result_str}
#{additional}
"""}
end
@impl AI.Tools
def spec do
%{
type: "function",
function: %{
name: "shell_tool",
description: """
Executes shell commands and returns the output. The user must approve
execution of this command before it is run. The user may optionally
approve the command for the entire session.
The following commands are always allowed without approval:
- `ls`
- `pwd`
- `find`
- `cat`
- `rg`
- `ag`
- `grep` (although take care, since it varies between bsd/macOS and GNU/Linux)
""",
parameters: %{
type: "object",
required: ["description", "cmd"],
properties: %{
description: %{
type: "string",
description: """
Explain to the user what the command does and why it is needed.
This will be displayed to the user in the approval dialog."
"""
},
cmd: %{
type: "string",
description: "The complete command to execute."
}
}
}
}
}
end
@impl AI.Tools
def call(args) do
with {:ok, desc} <- AI.Tools.get_arg(args, "description"),
{:ok, cmd} <- AI.Tools.get_arg(args, "cmd"),
{:ok, %{"cmd" => cmd, "args" => args, "approval_bits" => bits}} <- validate(cmd),
{:ok, :approved} <- confirm(desc, bits, cmd, args) do
call_shell_cmd(cmd, args)
end
end
defp call_shell_cmd(cmd, args) do
System.cmd(cmd, args, stderr_to_stdout: true, parallelism: true)
|> case do
{output, 0} -> {:ok, String.trim_trailing(output)}
{output, _} -> {:error, String.trim_trailing(output)}
end
end
defp validate(cmd) do
AI.Agent.ShellCmdParser.get_response(%{shell_cmd: cmd})
end
defp confirm(desc, _, "sh", args) do
full_cmd =
["sh" | args]
|> Enum.join(" ")
options = [
"Approve",
"Deny",
"Deny (with feedback)"
]
"""
The AI agent would like to execute a shell command.
# Command
```sh
#{full_cmd}
```
# Description and Purpose
> #{desc}
# Approval
_Complex commands involving pipes, redirection, command substitution, and
other shell features cannot be approved for the entire session. You must
approve each command individually._
"""
|> UI.choose(options)
|> case do
"Deny (with feedback)" ->
feedback = UI.prompt("Opine away:")
{:error,
"""
The user declined to approve the command. They responded with:
#{feedback}
"""}
"Deny" ->
{:error, "The user declined to approve the command."}
"Approve" ->
{:ok, :approved}
end
end
defp confirm(desc, approval_bits, cmd, args) do
full_cmd =
[cmd | args]
|> Enum.join(" ")
approval_str =
["Approve all for session:" | approval_bits]
|> Enum.join(" ")
key =
["shell_cmd" | approval_bits]
|> Enum.join("#")
options = [
"Approve",
approval_str,
"Deny",
"Deny (with feedback)"
]
key
|> Once.get()
|> case do
{:ok, :approved} ->
{:ok, :approved}
_ ->
"""
The AI agent would like to execute a shell command.
# Command
```sh
#{full_cmd}
```
# Description and Purpose
> #{desc}
# Approval
You can approve this call only, or you can approve all future calls for
this command and its subcommands in this session.
"""
|> UI.choose(options)
|> case do
"Deny (with feedback)" ->
feedback = UI.prompt("Opine away:")
{:error,
"""
The user declined to approve the command. They responded with:
#{feedback}
"""}
"Deny" ->
{:error, "The user declined to approve the command."}
"Approve" ->
{:ok, :approved}
^approval_str ->
Once.set(key, :approved)
{:ok, :approved}
end
end
end
end