Packages
fnord
0.9.36
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/message/user.ex
defmodule AI.Message.User do
@moduledoc """
User input message.
Internal shape (matches the chat-completions raw map shape that existing
pattern matches expect):
%AI.Message.User{role: "user", content: "<binary>"}
Wire shape (built on demand by `to_map/1`):
%{type: "message", role: "user",
content: [%{type: "input_text", text: "..."}]}
Storing `content` as a binary keeps the struct drop-in compatible with the
~150 pattern-match sites that do `msg.content` or
`%{role: "user", content: c} = msg`.
We already accept typed content parts on hydrate and collapse them back to a
single binary. Supporting rich multi-part user messages end-to-end would still
require revisiting the internal struct shape.
"""
@behaviour AI.Message
@derive {Jason.Encoder, only: [:role, :content]}
defstruct role: "user", content: ""
@type t :: %__MODULE__{role: binary(), content: binary()}
@doc "Build a user message from a binary."
@spec new(binary()) :: t()
def new(text) when is_binary(text), do: %__MODULE__{content: text}
@impl AI.Message
def text(%__MODULE__{content: c}), do: c
@impl AI.Message
def for_transcript(%__MODULE__{content: c}), do: "# USER:\n#{c}"
@impl AI.Message
def to_map(%__MODULE__{content: c}) do
%{type: "message", role: "user", content: [%{type: "input_text", text: c}]}
end
@doc """
Hydrate from a Responses-shaped or legacy chat-completions-shaped map
(atom or string keys). The Responses shape carries `content` as a list of
typed parts; we collapse those parts to a single binary.
"""
@spec from_map(map()) :: t()
def from_map(raw) do
content = AI.Message.get(raw, :content)
text =
case content do
list when is_list(list) ->
list
|> Enum.map(&part_text/1)
|> Enum.reject(&is_nil/1)
|> Enum.join("")
binary when is_binary(binary) ->
binary
nil ->
""
end
%__MODULE__{content: text}
end
defp part_text(%{} = part) do
case AI.Message.get(part, :text) do
txt when is_binary(txt) -> txt
_ -> nil
end
end
defp part_text(_), do: nil
end