Current section
Files
Jump to
Current section
Files
lib/codex/protocol/session_source.ex
defmodule Codex.Protocol.SessionSource do
@moduledoc """
Typed representation of thread/session source metadata returned by Codex.
This normalizes both core-session source values such as `"mcp"` and
app-server v2 values such as `"appServer"` into a single SDK-facing shape.
"""
use TypedStruct
alias Codex.Protocol.SubAgentSource
@type kind :: :cli | :vscode | :exec | :app_server | :sub_agent | :unknown
@type source_kind ::
:cli
| :vscode
| :exec
| :app_server
| :sub_agent
| :sub_agent_review
| :sub_agent_compact
| :sub_agent_thread_spawn
| :sub_agent_other
| :unknown
typedstruct do
field(:kind, kind(), enforce: true)
field(:sub_agent, SubAgentSource.t() | nil)
end
@spec from_map(map() | atom() | String.t() | t() | nil) :: t()
def from_map(%__MODULE__{} = source), do: source
def from_map(nil), do: %__MODULE__{kind: :unknown}
def from_map(value) when is_atom(value), do: value |> Atom.to_string() |> from_map()
def from_map(value) when is_binary(value) do
case normalize_source_kind(value) do
:cli -> %__MODULE__{kind: :cli}
:vscode -> %__MODULE__{kind: :vscode}
:exec -> %__MODULE__{kind: :exec}
:app_server -> %__MODULE__{kind: :app_server}
:sub_agent -> %__MODULE__{kind: :sub_agent, sub_agent: %SubAgentSource{variant: :other}}
_ -> %__MODULE__{kind: :unknown}
end
end
def from_map(%{} = value) do
normalized = normalize_keys(value)
if Map.has_key?(normalized, "sub_agent") do
%__MODULE__{
kind: :sub_agent,
sub_agent: normalized |> Map.get("sub_agent") |> SubAgentSource.from_map()
}
else
normalized
|> Map.get("type")
|> from_map()
end
end
@spec to_map(t()) :: map() | String.t()
def to_map(%__MODULE__{kind: :cli}), do: "cli"
def to_map(%__MODULE__{kind: :vscode}), do: "vscode"
def to_map(%__MODULE__{kind: :exec}), do: "exec"
def to_map(%__MODULE__{kind: :app_server}), do: "appServer"
def to_map(%__MODULE__{kind: :unknown}), do: "unknown"
def to_map(%__MODULE__{kind: :sub_agent, sub_agent: %SubAgentSource{} = source}) do
%{"subAgent" => SubAgentSource.to_map(source)}
end
def to_map(%__MODULE__{kind: :sub_agent, sub_agent: nil}) do
%{"subAgent" => %{"other" => "unknown"}}
end
@spec source_kind(map() | atom() | String.t() | t() | nil) :: source_kind()
def source_kind(value) do
case from_map(value) do
%__MODULE__{kind: kind} when kind in [:cli, :vscode, :exec, :app_server] ->
kind
%__MODULE__{kind: :sub_agent, sub_agent: sub_agent} ->
sub_agent_source_kind(sub_agent)
%__MODULE__{} ->
:unknown
end
end
@spec normalize_source_kind(atom() | String.t() | nil) :: source_kind() | nil
def normalize_source_kind(nil), do: nil
def normalize_source_kind(value) when is_atom(value),
do: normalize_source_kind(Atom.to_string(value))
def normalize_source_kind("cli"), do: :cli
def normalize_source_kind("vscode"), do: :vscode
def normalize_source_kind("vs_code"), do: :vscode
def normalize_source_kind("exec"), do: :exec
def normalize_source_kind("mcp"), do: :app_server
def normalize_source_kind("appServer"), do: :app_server
def normalize_source_kind("app_server"), do: :app_server
def normalize_source_kind("subAgent"), do: :sub_agent
def normalize_source_kind("sub_agent"), do: :sub_agent
def normalize_source_kind("subAgentReview"), do: :sub_agent_review
def normalize_source_kind("sub_agent_review"), do: :sub_agent_review
def normalize_source_kind("subAgentCompact"), do: :sub_agent_compact
def normalize_source_kind("sub_agent_compact"), do: :sub_agent_compact
def normalize_source_kind("subAgentThreadSpawn"), do: :sub_agent_thread_spawn
def normalize_source_kind("sub_agent_thread_spawn"), do: :sub_agent_thread_spawn
def normalize_source_kind("subAgentOther"), do: :sub_agent_other
def normalize_source_kind("sub_agent_other"), do: :sub_agent_other
def normalize_source_kind("unknown"), do: :unknown
def normalize_source_kind(_), do: :unknown
defp normalize_keys(%{} = map) do
map
|> Enum.map(fn {key, value} ->
{normalize_key(key), normalize_value(value)}
end)
|> Map.new()
end
defp normalize_value(%{} = value), do: normalize_keys(value)
defp normalize_value(value), do: value
defp normalize_key(key) when is_atom(key), do: key |> Atom.to_string() |> normalize_key()
defp normalize_key("subAgent"), do: "sub_agent"
defp normalize_key("subagent"), do: "sub_agent"
defp normalize_key(key) when is_binary(key), do: key
defp sub_agent_source_kind(%SubAgentSource{variant: :review}), do: :sub_agent_review
defp sub_agent_source_kind(%SubAgentSource{variant: :compact}), do: :sub_agent_compact
defp sub_agent_source_kind(%SubAgentSource{variant: :thread_spawn}), do: :sub_agent_thread_spawn
defp sub_agent_source_kind(%SubAgentSource{variant: :other}), do: :sub_agent_other
defp sub_agent_source_kind(%SubAgentSource{}), do: :sub_agent
defp sub_agent_source_kind(_), do: :sub_agent
end