Packages
fnord
0.9.39
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/tasks/resolve_task.ex
defmodule AI.Tools.Tasks.ResolveTask do
@moduledoc """
Tool to resolve a task as success or failure in a Services.Task list.
"""
@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) when is_map(args) do
with {:ok, raw_id} <- AI.Tools.get_arg(args, "list_id"),
{:ok, list_id} <-
(case raw_id do
id when is_integer(id) -> {:ok, Integer.to_string(id)}
id when is_binary(id) -> {:ok, id}
_ -> {:error, :invalid_argument, "list_id"}
end),
{:ok, task_id} <- AI.Tools.get_arg(args, "task_id"),
true <- is_binary(task_id) or {:error, :invalid_argument, "task_id must be a string"},
task_id <- String.trim(task_id),
true <- task_id != "" or {:error, :invalid_argument, "task_id cannot be empty"},
{:ok, disposition} <- AI.Tools.get_arg(args, "disposition"),
true <-
disposition in ["success", "failure"] or
{:error, :invalid_argument, "disposition must be 'success' or 'failure'"},
{:ok, result} <- AI.Tools.get_arg(args, "result"),
true <- is_binary(result) or {:error, :invalid_argument, "result must be a string"} do
{:ok,
%{
"list_id" => list_id,
"task_id" => task_id,
"disposition" => disposition,
"result" => result
}}
end
end
@impl AI.Tools
def ui_note_on_request(%{
"list_id" => list_id,
"task_id" => task_id,
"disposition" => disposition
}) do
# Suppress the UI note entirely if the task is already resolved.
# LLMs routinely re-resolve tasks; this is an expected condition,
# not worth logging.
if task_already_resolved?(list_id, task_id) do
nil
else
glyph = if disposition == "success", do: "✓", else: "✗"
case get_task_counts(list_id) do
{total, resolved} ->
{"Task resolved", Util.truncate_chars("(#{resolved + 1}/#{total}) #{glyph} #{task_id}")}
nil ->
{"Task resolved", Util.truncate_chars("#{glyph} #{task_id}")}
end
end
end
@impl AI.Tools
def ui_note_on_result(_args, _result), do: nil
@impl AI.Tools
def tool_call_failure_message(_args, _reason), do: :default
@impl AI.Tools
def spec do
%{
type: "function",
name: "tasks_resolve_task",
description: """
Resolve a task in an existing task list as either success or failure.
The disposition and result are visible to the user, so ensure that the
result is clear and informative.
Include as much detail as possible in the event of a problem. This will
help guide the user in appropriate next steps to resolve the issue.
""",
parameters: %{
type: "object",
additionalProperties: false,
required: ["list_id", "task_id", "disposition", "result"],
properties: %{
"list_id" => %{
type: "string",
description: "The ID of the task list (string; integers accepted)."
},
"task_id" => %{
type: "string",
description: "Identifier of the task to resolve."
},
"disposition" => %{
type: "string",
enum: ["success", "failure"],
description: "Outcome of the task"
},
"result" => %{
type: "string",
description: "Outcome message used for either success or failure"
}
}
}
}
end
@impl AI.Tools
def call(%{
"list_id" => list_id,
"task_id" => task_id,
"disposition" => disp,
"result" => result
}) do
resolve_result =
case disp do
"success" -> Services.Task.complete_task(list_id, task_id, result)
"failure" -> Services.Task.fail_task(list_id, task_id, result)
end
# Duplicate resolution is an expected LLM behavior - return a gentle
# nudge rather than an error so the tool framework doesn't log errors
# or alarm the user.
case resolve_result do
:ok ->
{:ok, Services.Task.as_string(list_id)}
{:error, :already_resolved} ->
{:ok, "Task '#{task_id}' has already been resolved."}
{:error, :not_found} ->
{:ok, "Task '#{task_id}' was not found in list '#{list_id}'."}
end
end
# ----------------------------------------------------------------------------
# Private Functions
# ----------------------------------------------------------------------------
# Checks whether the given task is already in a terminal state (done/failed).
# Returns false if the task service is unavailable or the task doesn't exist.
defp task_already_resolved?(list_id, task_id) do
case Services.Instance.whereis(Services.Task) do
nil ->
false
_pid ->
list_id
|> Services.Task.get_list()
|> case do
{:error, _} -> []
tasks -> tasks
end
|> Enum.any?(fn %{id: id, outcome: outcome} -> id == task_id and outcome != :todo end)
end
end
# Returns {total, resolved} task counts if the Services.Task GenServer is
# running, or nil if it's not available (e.g., during replay).
defp get_task_counts(list_id) do
case Services.Instance.whereis(Services.Task) do
nil ->
nil
_pid ->
list_id
|> Services.Task.get_list()
|> case do
{:error, _reason} -> []
tasks -> tasks
end
|> Enum.reduce({0, 0}, fn
%{outcome: :todo}, {t, r} -> {t + 1, r}
_, {t, r} -> {t + 1, r + 1}
end)
end
end
end