Packages
fnord
0.9.40
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/skills/skill.ex
defmodule Skills.Skill do
@moduledoc """
A single skill definition loaded from a TOML file.
Skills are defined on disk as TOML and loaded at runtime.
Fields in the struct reflect the stable skill schema:
- `name`, `description`, `model`, `tools`, `system_prompt` are required
- `response_format` is optional
Validation is performed when loading a skill from TOML.
"""
@type t :: %__MODULE__{
name: String.t(),
description: String.t(),
model: String.t(),
tools: [String.t()],
system_prompt: String.t(),
response_format: map() | nil
}
defstruct [:name, :description, :model, :tools, :system_prompt, :response_format]
@type decode_error ::
{:missing_key, String.t()}
| {:invalid_type, String.t(), expected :: String.t(), got :: term()}
| {:invalid_value, String.t(), term()}
@doc """
Convert a decoded TOML map into a validated skill struct.
The TOML parser is expected to decode keys as strings.
"""
@type warning :: {:dropped_non_strings, key :: String.t(), dropped :: [term()]}
@spec from_map(map()) :: {:ok, t(), [warning]} | {:error, decode_error}
def from_map(map) when is_map(map) do
with {:ok, name} <- required_string(map, "name"),
{:ok, description} <- required_string(map, "description"),
{:ok, model} <- required_string(map, "model"),
{:ok, tools, tools_warnings} <- required_string_list(map, "tools"),
:ok <- require_basic_tag(tools),
{:ok, system_prompt} <- required_string(map, "system_prompt"),
{:ok, response_format} <- optional_map(map, "response_format") do
{:ok,
%__MODULE__{
name: name,
description: description,
model: model,
tools: tools,
system_prompt: system_prompt,
response_format: response_format
}, tools_warnings}
end
end
defp required_string(map, key) do
case Map.fetch(map, key) do
:error -> {:error, {:missing_key, key}}
{:ok, value} when is_binary(value) and byte_size(value) > 0 -> {:ok, value}
{:ok, value} when is_binary(value) -> {:error, {:invalid_value, key, value}}
{:ok, value} -> {:error, {:invalid_type, key, "string", value}}
end
end
defp required_string_list(map, key) do
case Map.fetch(map, key) do
:error ->
{:error, {:missing_key, key}}
{:ok, list} when is_list(list) ->
{strings, non_strings} = Enum.split_with(list, &is_binary/1)
warnings =
case non_strings do
[] -> []
_ -> [{:dropped_non_strings, key, non_strings}]
end
case strings do
[] -> {:error, {:invalid_value, key, list}}
_ -> {:ok, strings, warnings}
end
{:ok, value} ->
{:error, {:invalid_type, key, "array of strings", value}}
end
end
# Fail at load time rather than deferring to runtime, where the error would
# be surprising and harder to diagnose.
defp require_basic_tag(tools) do
if "basic" in tools do
:ok
else
{:error, {:invalid_value, "tools", "must include \"basic\" tag, got: #{inspect(tools)}"}}
end
end
defp optional_map(map, key) do
case Map.fetch(map, key) do
:error -> {:ok, nil}
{:ok, nil} -> {:ok, nil}
{:ok, value} when is_map(value) -> {:ok, value}
{:ok, value} -> {:error, {:invalid_type, key, "table/map", value}}
end
end
end