Current section

Files

Jump to
claude_code lib claude_code message result.ex
Raw

lib/claude_code/message/result.ex

defmodule ClaudeCode.Message.Result do
@moduledoc """
Represents a result message from the Claude CLI.
Result messages are the final message in a conversation, containing
the final response, timing information, token usage, and cost.
Matches the official SDK schema for successful results:
```
{
type: "result",
subtype: "success",
duration_ms: float,
duration_api_ms: float,
is_error: boolean,
num_turns: int,
result: string,
session_id: string,
total_cost_usd: float
}
```
And for error results:
```
{
type: "result",
subtype: "error_max_turns" | "error_during_execution",
duration_ms: float,
duration_api_ms: float,
is_error: boolean,
num_turns: int,
session_id: string,
total_cost_usd: float
}
```
"""
alias ClaudeCode.Types
@enforce_keys [
:type,
:subtype,
:is_error,
:duration_ms,
:duration_api_ms,
:num_turns,
:result,
:session_id,
:total_cost_usd,
:usage
]
defstruct [
:type,
:subtype,
:is_error,
:duration_ms,
:duration_api_ms,
:num_turns,
:result,
:session_id,
:total_cost_usd,
:usage
]
@type t :: %__MODULE__{
type: :result,
subtype: Types.result_subtype(),
is_error: boolean(),
duration_ms: float(),
duration_api_ms: float(),
num_turns: non_neg_integer(),
result: String.t(),
session_id: Types.session_id(),
total_cost_usd: float(),
usage: Types.usage()
}
@doc """
Creates a new Result message from JSON data.
## Examples
iex> Result.new(%{"type" => "result", "subtype" => "success", ...})
{:ok, %Result{...}}
iex> Result.new(%{"type" => "assistant"})
{:error, :invalid_message_type}
"""
@spec new(map()) :: {:ok, t()} | {:error, atom() | {:missing_fields, [atom()]}}
def new(%{"type" => "result"} = json) do
required_fields = [
"subtype",
"is_error",
"duration_ms",
"duration_api_ms",
"num_turns",
"result",
"session_id",
"total_cost_usd",
"usage"
]
missing = Enum.filter(required_fields, &(not Map.has_key?(json, &1)))
if Enum.empty?(missing) do
message = %__MODULE__{
type: :result,
subtype: parse_subtype(json["subtype"]),
is_error: json["is_error"],
duration_ms: json["duration_ms"],
duration_api_ms: json["duration_api_ms"],
num_turns: json["num_turns"],
result: json["result"],
session_id: json["session_id"],
total_cost_usd: parse_float(json["total_cost_usd"]),
usage: parse_usage(json["usage"])
}
{:ok, message}
else
{:error, {:missing_fields, Enum.map(missing, &String.to_atom/1)}}
end
end
def new(_), do: {:error, :invalid_message_type}
@doc """
Type guard to check if a value is a Result message.
"""
@spec result_message?(any()) :: boolean()
def result_message?(%__MODULE__{type: :result}), do: true
def result_message?(_), do: false
defp parse_subtype("success"), do: :success
defp parse_subtype("error_max_turns"), do: :error_max_turns
defp parse_subtype("error_during_execution"), do: :error_during_execution
defp parse_subtype(other) when is_binary(other), do: String.to_atom(other)
defp parse_float(value) when is_float(value), do: value
defp parse_float(value) when is_integer(value), do: value * 1.0
defp parse_float(_), do: 0.0
defp parse_usage(usage_data) when is_map(usage_data) do
%{
input_tokens: usage_data["input_tokens"] || 0,
cache_creation_input_tokens: usage_data["cache_creation_input_tokens"] || 0,
cache_read_input_tokens: usage_data["cache_read_input_tokens"] || 0,
output_tokens: usage_data["output_tokens"] || 0,
server_tool_use: parse_server_tool_use(usage_data["server_tool_use"])
}
end
defp parse_usage(_),
do: %{
input_tokens: 0,
cache_creation_input_tokens: 0,
cache_read_input_tokens: 0,
output_tokens: 0,
server_tool_use: %{web_search_requests: 0}
}
defp parse_server_tool_use(%{"web_search_requests" => count}), do: %{web_search_requests: count}
defp parse_server_tool_use(_), do: %{web_search_requests: 0}
end