Packages
fnord
0.8.74
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/add_task.ex
defmodule AI.Tools.Tasks.AddTask do
@moduledoc """
Tool to add a new task to 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, list_id} <- AI.Tools.get_arg(args, "list_id"),
true <- is_integer(list_id) or {:error, :invalid_argument, "list_id must be an integer"} do
case Map.fetch(args, "tasks") do
{:ok, tasks} ->
with {:ok, normalized} <- validate_and_normalize_tasks(tasks) do
{:ok, %{"list_id" => list_id, "tasks" => normalized}}
end
:error ->
with {: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"},
{:ok, data} <- AI.Tools.get_arg(args, "data"),
true <- is_binary(data) or {:error, :invalid_argument, "data must be a string"} do
{:ok, %{"list_id" => list_id, "tasks" => [%{"task_id" => task_id, "data" => data}]}}
end
end
end
end
@impl AI.Tools
def ui_note_on_request(%{"tasks" => [%{"task_id" => task_id, "data" => data}]}) do
{task_id, data}
end
@impl AI.Tools
def ui_note_on_request(%{"tasks" => tasks}) when is_list(tasks) do
count = length(tasks)
ids = Enum.map(tasks, & &1["task_id"])
preview = ids |> Enum.take(3) |> Enum.join(", ")
{"Adding #{count} tasks", preview}
end
@impl AI.Tools
def ui_note_on_request(%{"task_id" => task_id, "data" => data}) do
{task_id, data}
end
@impl AI.Tools
def ui_note_on_result(%{"tasks" => tasks}, _result) when is_list(tasks) do
count = length(tasks)
"Added #{count} task(s)"
end
@impl AI.Tools
def ui_note_on_result(%{"task_id" => _task_id}, _result) do
"Added 1 task"
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",
function: %{
name: "tasks_add_task",
description: """
Add a new task to an existing task list. The task is identified by a
unique task_id that should be a short label, describing the task at a
glance (e.g. "Add some_function/3 to Some.Module") Note that this is
NOT a "slug" - it's a human-readable label. The data field is a
free-form string that can contain any details or payload for the task.
Tasks are intended to help you retain state between interactions, even
if your context window is full. Ensure that the data field includes
enough context for you to understand and complete the task later, even
if you have forgotten the details you had in mind when creating it.
Tasks are added IN ORDER - the first task in the list will be the
next task to be completed.
""",
parameters: %{
type: "object",
additionalProperties: false,
required: ["list_id"],
properties: %{
"list_id" => %{
type: :integer,
description: "The ID of the task list."
},
"task_id" => %{
type: "string",
description: """
A short task label that describes the task. This doubles as the
unique identifier for the task. Examples:
- "Add some_function/3 to Some.Module"
- "Write tests for Another.Module"
- "Identify stale documentation in Some.Module"
"""
},
"data" => %{
type: "string",
description: "Free-form detail or payload for the task."
},
"tasks" => %{
type: "array",
description: "A list of tasks to add, each with a task_id and data.",
items: %{
type: "object",
additionalProperties: false,
required: ["task_id", "data"],
properties: %{
"task_id" => %{
type: "string",
description: """
A short task label that describes the task. This doubles as the
unique identifier for the task. Examples:
- "Add some_function/3 to Some.Module"
- "Write tests for Another.Module"
- "Identify stale documentation in Some.Module"
"""
},
"data" => %{
type: "string",
description: "The detail or payload for the task."
}
}
}
}
}
}
}
}
end
@impl AI.Tools
def call(%{"list_id" => list_id, "tasks" => tasks}) when is_list(tasks) do
Enum.each(tasks, fn %{"task_id" => task_id, "data" => data} ->
Services.Task.add_task(list_id, task_id, data)
end)
{:ok, Services.Task.as_string(list_id)}
end
@impl AI.Tools
def call(%{"list_id" => list_id, "task_id" => task_id, "data" => data}) do
Services.Task.add_task(list_id, task_id, data)
{:ok, Services.Task.as_string(list_id)}
end
# Validate and normalize a list of tasks into the required format or return an error tuple.
defp validate_and_normalize_tasks(tasks) do
cond do
not is_list(tasks) ->
{:error, :invalid_argument, "tasks must be a list of task objects"}
tasks == [] ->
{:error, :invalid_argument, "tasks list cannot be empty"}
true ->
tasks
|> Enum.reduce_while({:ok, []}, fn
%{"task_id" => task_id, "data" => data}, {:ok, acc}
when is_binary(task_id) and is_binary(data) ->
{:cont, {:ok, [%{"task_id" => task_id, "data" => data} | acc]}}
%{"task_id" => _, "data" => _}, _ ->
{:halt, {:error, :invalid_argument, "each task must have string task_id and data"}}
_, _ ->
{:halt,
{:error, :invalid_argument,
"tasks must be list of %{\"task_id\" => string, \"data\" => string}"}}
end)
|> case do
{:ok, list} -> {:ok, Enum.reverse(list)}
error -> error
end
end
end
end