Packages
claude_code
0.24.0
0.36.5
0.36.4
0.36.3
0.36.2
0.36.1
0.36.0
0.35.0
0.34.0
0.33.1
0.32.2
0.32.0
0.31.0
0.30.0
0.29.0
0.28.0
0.27.0
0.26.0
0.25.0
0.24.0
0.23.0
0.22.0
0.21.0
0.20.0
0.19.0
0.18.0
0.17.0
0.16.0
0.15.0
0.14.0
0.13.3
0.13.2
0.13.1
0.13.0
0.12.0
0.11.0
0.10.0
0.9.0
0.8.1
0.8.0
0.7.0
0.6.0
0.5.0
0.4.0
0.3.0
0.2.0
0.1.0
Claude Agent SDK for Elixir – Build AI agents with Claude Code
Current section
Files
Jump to
Current section
Files
lib/claude_code/cli/parser.ex
defmodule ClaudeCode.CLI.Parser do
@moduledoc """
Parses CLI JSON output into message and content structs.
This module is the CLI protocol layer responsible for converting
newline-delimited JSON from `--output-format stream-json` into
the adapter-agnostic struct types defined in `ClaudeCode.Message.*`
and `ClaudeCode.Content.*`.
A future native API adapter would produce the same structs but from
a different wire format. The struct definitions and type-checking
functions remain in `ClaudeCode.Message` and `ClaudeCode.Content`.
"""
alias ClaudeCode.Content.TextBlock
alias ClaudeCode.Content.ThinkingBlock
alias ClaudeCode.Content.ToolResultBlock
alias ClaudeCode.Content.ToolUseBlock
alias ClaudeCode.Message.AssistantMessage
alias ClaudeCode.Message.CompactBoundaryMessage
alias ClaudeCode.Message.PartialAssistantMessage
alias ClaudeCode.Message.ResultMessage
alias ClaudeCode.Message.SystemMessage
alias ClaudeCode.Message.UserMessage
# -- Message parsing --------------------------------------------------------
@doc """
Parses a decoded JSON map into a message struct.
Dispatches on `"type"` to the appropriate message module's `new/1`
constructor.
## Examples
iex> ClaudeCode.CLI.Parser.parse_message(%{"type" => "system", "subtype" => "init", ...})
{:ok, %ClaudeCode.Message.SystemMessage{...}}
iex> ClaudeCode.CLI.Parser.parse_message(%{"type" => "unknown"})
{:error, {:unknown_message_type, "unknown"}}
"""
@spec parse_message(map()) :: {:ok, ClaudeCode.Message.t()} | {:error, term()}
def parse_message(%{"type" => type} = data) do
case type do
"system" -> parse_system(data)
"assistant" -> AssistantMessage.new(data)
"user" -> UserMessage.new(data)
"result" -> ResultMessage.new(data)
"stream_event" -> PartialAssistantMessage.new(data)
other -> {:error, {:unknown_message_type, other}}
end
end
def parse_message(_), do: {:error, :missing_type}
@doc """
Parses a list of decoded JSON maps into message structs.
Returns `{:ok, messages}` if all messages parse successfully,
or `{:error, {:parse_error, index, error}}` for the first failure.
"""
@spec parse_all_messages(list(map())) :: {:ok, [ClaudeCode.Message.t()]} | {:error, term()}
def parse_all_messages(messages) when is_list(messages) do
messages
|> Enum.with_index()
|> Enum.reduce_while({:ok, []}, fn {message, index}, {:ok, acc} ->
case parse_message(message) do
{:ok, parsed} -> {:cont, {:ok, [parsed | acc]}}
{:error, error} -> {:halt, {:error, {:parse_error, index, error}}}
end
end)
|> case do
{:ok, parsed} -> {:ok, Enum.reverse(parsed)}
error -> error
end
end
@doc """
Parses a newline-delimited JSON stream from the CLI.
This is the format output by the CLI with `--output-format stream-json`.
Each line is a complete JSON object representing a single message.
"""
@spec parse_stream(String.t()) :: {:ok, [ClaudeCode.Message.t()]} | {:error, term()}
def parse_stream(stream) when is_binary(stream) do
stream
|> String.split("\n", trim: true)
|> Enum.with_index()
|> Enum.reduce_while({:ok, []}, fn {line, index}, {:ok, acc} ->
case Jason.decode(line) do
{:ok, json} ->
case parse_message(json) do
{:ok, message} -> {:cont, {:ok, [message | acc]}}
{:error, error} -> {:halt, {:error, {:parse_error, index, error}}}
end
{:error, error} ->
{:halt, {:error, {:json_decode_error, index, error}}}
end
end)
|> case do
{:ok, messages} -> {:ok, Enum.reverse(messages)}
error -> error
end
end
# -- Content parsing --------------------------------------------------------
@doc """
Parses a decoded JSON map into a content block struct.
Dispatches on `"type"` to the appropriate content module's `new/1`
constructor.
## Examples
iex> ClaudeCode.CLI.Parser.parse_content(%{"type" => "text", "text" => "Hello"})
{:ok, %ClaudeCode.Content.TextBlock{type: :text, text: "Hello"}}
iex> ClaudeCode.CLI.Parser.parse_content(%{"type" => "unknown"})
{:error, {:unknown_content_type, "unknown"}}
"""
@spec parse_content(map()) :: {:ok, ClaudeCode.Content.t()} | {:error, term()}
def parse_content(%{"type" => type} = data) do
case type do
"text" -> TextBlock.new(data)
"thinking" -> ThinkingBlock.new(data)
"tool_use" -> ToolUseBlock.new(data)
"tool_result" -> ToolResultBlock.new(data)
other -> {:error, {:unknown_content_type, other}}
end
end
def parse_content(_), do: {:error, :missing_type}
@doc """
Parses a list of decoded JSON maps into content block structs.
Returns `{:ok, contents}` if all blocks parse successfully,
or `{:error, {:parse_error, index, error}}` for the first failure.
"""
@spec parse_all_contents(list(map())) :: {:ok, [ClaudeCode.Content.t()]} | {:error, term()}
def parse_all_contents(blocks) when is_list(blocks) do
blocks
|> Enum.with_index()
|> Enum.reduce_while({:ok, []}, fn {block, index}, {:ok, acc} ->
case parse_content(block) do
{:ok, content} -> {:cont, {:ok, [content | acc]}}
{:error, error} -> {:halt, {:error, {:parse_error, index, error}}}
end
end)
|> case do
{:ok, contents} -> {:ok, Enum.reverse(contents)}
error -> error
end
end
# -- Private: system message dispatch ---------------------------------------
defp parse_system(%{"subtype" => "compact_boundary"} = data) do
CompactBoundaryMessage.new(data)
end
defp parse_system(%{"subtype" => _} = data) do
SystemMessage.new(data)
end
defp parse_system(_), do: {:error, :invalid_system_subtype}
end