defmodule Jido.Chat.Serialization do
@moduledoc false
alias Jido.Chat.{
ActionEvent,
AssistantContextChangedEvent,
AssistantThreadStartedEvent,
Card,
CapabilityMatrix,
ChannelRef,
EventEnvelope,
FileUpload,
Incoming,
IngressResult,
Markdown,
Message,
Modal,
ModalCloseEvent,
ModalResult,
ModalSubmitEvent,
ModalResponse,
PostPayload,
ReactionEvent,
SentMessage,
SlashCommandEvent,
StreamChunk,
Thread,
WebhookRequest,
WebhookResponse,
Wire
}
@spec to_map(map()) :: map()
def to_map(chat) when is_map(chat) do
snapshot = Jido.Chat.StateAdapter.snapshot(chat.state_adapter, chat.state)
%{
id: chat.id,
user_name: chat.user_name,
adapters: serialize_adapters(chat.adapters),
state_adapter: Wire.encode_module(chat.state_adapter),
subscriptions: chat.subscriptions |> MapSet.to_list() |> Enum.sort(),
dedupe: serialize_dedupe(chat.dedupe),
dedupe_order: serialize_dedupe_order(chat.dedupe_order || []),
handlers: serialize_handlers(chat.handlers),
metadata: Wire.to_plain(chat.metadata),
thread_state: Wire.to_plain(chat.thread_state),
channel_state: Wire.to_plain(chat.channel_state),
locks: Wire.to_plain(snapshot.locks),
pending_locks: Wire.to_plain(snapshot.pending_locks),
initialized: chat.initialized
}
|> Wire.to_plain()
|> Map.put("__type__", "chat")
end
@spec from_map(map()) :: map()
def from_map(map) when is_map(map) do
chat =
Jido.Chat.new(%{
id: map[:id] || map["id"],
user_name: map[:user_name] || map["user_name"],
adapters: deserialize_adapters(map[:adapters] || map["adapters"] || %{}),
state_adapter: map[:state_adapter] || map["state_adapter"],
metadata: map[:metadata] || map["metadata"] || %{},
subscriptions: map[:subscriptions] || map["subscriptions"] || [],
dedupe: map[:dedupe] || map["dedupe"] || [],
dedupe_order: map[:dedupe_order] || map["dedupe_order"] || [],
thread_state: map[:thread_state] || map["thread_state"] || %{},
channel_state: map[:channel_state] || map["channel_state"] || %{},
locks: map[:locks] || map["locks"] || %{},
pending_locks: map[:pending_locks] || map["pending_locks"] || %{}
})
%{
chat
| initialized: map[:initialized] || map["initialized"] || false,
handlers: deserialize_handlers(map[:handlers] || map["handlers"] || %{}, chat.handlers)
}
end
@spec reviver() :: (map() -> term())
def reviver, do: &revive/1
@spec revive(map()) :: term()
def revive(%{"__type__" => "chat"} = map), do: from_map(map)
def revive(%{"__type__" => "thread"} = map), do: Thread.from_map(map)
def revive(%{"__type__" => "channel"} = map), do: ChannelRef.from_map(map)
def revive(%{"__type__" => "incoming"} = map), do: Incoming.from_map(map)
def revive(%{"__type__" => "reaction_event"} = map), do: ReactionEvent.from_map(map)
def revive(%{"__type__" => "action_event"} = map), do: ActionEvent.from_map(map)
def revive(%{"__type__" => "modal_submit_event"} = map), do: ModalSubmitEvent.from_map(map)
def revive(%{"__type__" => "modal_close_event"} = map), do: ModalCloseEvent.from_map(map)
def revive(%{"__type__" => "slash_command_event"} = map), do: SlashCommandEvent.from_map(map)
def revive(%{"__type__" => "assistant_thread_started_event"} = map),
do: AssistantThreadStartedEvent.from_map(map)
def revive(%{"__type__" => "assistant_context_changed_event"} = map),
do: AssistantContextChangedEvent.from_map(map)
def revive(%{"__type__" => "markdown"} = map), do: Markdown.from_map(map)
def revive(%{"__type__" => "markdown_node"} = map), do: Jido.Chat.Markdown.Node.from_map(map)
def revive(%{"__type__" => "card"} = map), do: Card.from_map(map)
def revive(%{"__type__" => "card_component"} = map), do: Jido.Chat.Card.Component.from_map(map)
def revive(%{"__type__" => "modal"} = map), do: Modal.from_map(map)
def revive(%{"__type__" => "modal_element"} = map), do: Jido.Chat.Modal.Element.from_map(map)
def revive(%{"__type__" => "message"} = map), do: Message.from_map(map)
def revive(%{"__type__" => "file_upload"} = map), do: FileUpload.from_map(map)
def revive(%{"__type__" => "post_payload"} = map), do: PostPayload.from_map(map)
def revive(%{"__type__" => "sent_message"} = map), do: SentMessage.from_map(map)
def revive(%{"__type__" => "stream_chunk"} = map), do: StreamChunk.from_map(map)
def revive(%{"__type__" => "event_envelope"} = map), do: EventEnvelope.from_map(map)
def revive(%{"__type__" => "ingress_result"} = map), do: IngressResult.from_map(map)
def revive(%{"__type__" => "modal_result"} = map), do: ModalResult.from_map(map)
def revive(%{"__type__" => "modal_response"} = map), do: ModalResponse.from_map(map)
def revive(%{"__type__" => "capability_matrix"} = map), do: CapabilityMatrix.from_map(map)
def revive(%{"__type__" => "webhook_request"} = map), do: WebhookRequest.from_map(map)
def revive(%{"__type__" => "webhook_response"} = map), do: WebhookResponse.from_map(map)
def revive(map), do: map
defp serialize_adapters(adapters) when is_map(adapters) do
adapters
|> Enum.map(fn {name, module} -> {to_string(name), Wire.encode_module(module)} end)
|> Map.new()
end
defp serialize_dedupe(%MapSet{} = dedupe) do
dedupe
|> Enum.map(fn {adapter_name, message_id} ->
[to_string(adapter_name), to_string(message_id)]
end)
|> Enum.sort()
end
defp serialize_handlers(handlers) when is_map(handlers) do
counts =
handlers
|> Enum.map(fn {key, value} ->
{to_string(key), if(is_list(value), do: length(value), else: 0)}
end)
|> Map.new()
%{"serializable" => false, "counts" => counts}
end
defp deserialize_adapters(adapters) when is_map(adapters) do
adapters
|> Enum.map(fn {key, value} ->
{normalize_key_atom(key), Wire.decode_module(value)}
end)
|> Enum.reject(fn {_key, value} -> is_nil(value) end)
|> Map.new()
end
defp deserialize_adapters(_), do: %{}
defp serialize_dedupe_order(dedupe_order) when is_list(dedupe_order) do
dedupe_order
|> Enum.map(fn
{adapter_name, message_id} -> [to_string(adapter_name), to_string(message_id)]
[adapter_name, message_id] -> [to_string(adapter_name), to_string(message_id)]
_other -> nil
end)
|> Enum.reject(&is_nil/1)
end
defp serialize_dedupe_order(_), do: []
defp deserialize_handlers(handlers, defaults) when is_map(handlers) and is_map(defaults) do
serializable? = handlers[:serializable] || handlers["serializable"] || false
if serializable?, do: defaults, else: defaults
end
defp deserialize_handlers(_handlers, defaults), do: defaults
defp normalize_key_atom(key) when is_atom(key), do: key
defp normalize_key_atom(key) when is_binary(key) do
String.to_atom(key)
end
defp normalize_key_atom(key), do: key
end