Current section
Files
Jump to
Current section
Files
lib/codex/app_server/item_adapter.ex
defmodule Codex.AppServer.ItemAdapter do
@moduledoc false
alias Codex.Items
@spec to_item(map()) :: {:ok, Items.t()} | {:raw, map()}
def to_item(%{"type" => "userMessage"} = item) do
{:ok,
%Items.UserMessage{
id: Map.get(item, "id"),
content: Map.get(item, "content") || []
}}
end
def to_item(%{"type" => "agentMessage"} = item) do
{:ok,
%Items.AgentMessage{
id: Map.get(item, "id"),
text: Map.get(item, "text") || ""
}}
end
def to_item(%{"type" => "reasoning"} = item) do
{:ok,
%Items.Reasoning{
id: Map.get(item, "id"),
text: join_reasoning_text(Map.get(item, "summary"), Map.get(item, "content"))
}}
end
def to_item(%{"type" => "commandExecution"} = item) do
{:ok,
%Items.CommandExecution{
id: Map.get(item, "id"),
command: Map.get(item, "command") || "",
cwd: Map.get(item, "cwd"),
process_id: Map.get(item, "processId"),
command_actions: Map.get(item, "commandActions") || [],
aggregated_output: Map.get(item, "aggregatedOutput") || "",
exit_code: Map.get(item, "exitCode"),
status: normalize_status(Map.get(item, "status")),
duration_ms: Map.get(item, "durationMs")
}}
end
def to_item(%{"type" => "fileChange"} = item) do
{:ok,
%Items.FileChange{
id: Map.get(item, "id"),
status: normalize_status(Map.get(item, "status")),
changes: normalize_file_changes(Map.get(item, "changes") || [])
}}
end
def to_item(%{"type" => "mcpToolCall"} = item) do
{:ok,
%Items.McpToolCall{
id: Map.get(item, "id"),
server: Map.get(item, "server") || "",
tool: Map.get(item, "tool") || "",
arguments: Map.get(item, "arguments"),
result: Map.get(item, "result"),
error: Map.get(item, "error"),
status: normalize_status(Map.get(item, "status")),
duration_ms: Map.get(item, "durationMs")
}}
end
def to_item(%{"type" => "webSearch"} = item) do
{:ok,
%Items.WebSearch{
id: Map.get(item, "id"),
query: Map.get(item, "query") || ""
}}
end
def to_item(%{"type" => "imageView"} = item) do
{:ok,
%Items.ImageView{
id: Map.get(item, "id"),
path: Map.get(item, "path") || ""
}}
end
def to_item(%{"type" => "enteredReviewMode"} = item) do
{:ok,
%Items.ReviewMode{
id: Map.get(item, "id"),
entered: true,
review: Map.get(item, "review") || ""
}}
end
def to_item(%{"type" => "exitedReviewMode"} = item) do
{:ok,
%Items.ReviewMode{
id: Map.get(item, "id"),
entered: false,
review: Map.get(item, "review") || ""
}}
end
def to_item(%{"type" => _type} = item), do: {:raw, item}
def to_item(item) when is_map(item), do: {:raw, item}
defp normalize_file_changes(changes) when is_list(changes) do
Enum.map(changes, fn change ->
%{path: Map.get(change, "path") || "", kind: :update}
|> maybe_put(:diff, Map.get(change, "diff"))
|> then(fn base ->
kind = Map.get(change, "kind")
{kind_atom, move_path} = normalize_patch_change_kind(kind)
base
|> Map.put(:kind, kind_atom)
|> maybe_put(:move_path, move_path)
end)
end)
end
defp normalize_file_changes(_changes), do: []
defp normalize_patch_change_kind(%{"type" => "add"}), do: {:add, nil}
defp normalize_patch_change_kind(%{"type" => "delete"}), do: {:delete, nil}
defp normalize_patch_change_kind(%{"type" => "update"} = kind),
do: {:update, Map.get(kind, "movePath")}
defp normalize_patch_change_kind(_kind), do: {:update, nil}
defp normalize_status(nil), do: :in_progress
defp normalize_status(value) when is_atom(value), do: value
defp normalize_status(value) when is_binary(value) do
case value do
"inProgress" -> :in_progress
"completed" -> :completed
"failed" -> :failed
"declined" -> :declined
_other -> :in_progress
end
end
defp normalize_status(_), do: :in_progress
defp join_reasoning_text(summary, content) do
parts =
[]
|> maybe_concat_lines(summary)
|> maybe_concat_lines(content)
if parts == [] do
""
else
Enum.join(parts, "\n")
end
end
defp maybe_concat_lines(parts, lines) when is_list(lines) do
parts ++ Enum.map(lines, &to_string/1)
end
defp maybe_concat_lines(parts, _lines), do: parts
defp maybe_put(map, _key, nil), do: map
defp maybe_put(map, key, value), do: Map.put(map, key, value)
end