Packages

Prompt Runner SDK - packet-first prompt execution for Elixir and CLI workflows with verifier-owned completion, retry, repair, and git-aware repository orchestration.

Current section

Files

Jump to
prompt_runner_sdk lib prompt_runner rendering renderers verbose.ex
Raw

lib/prompt_runner/rendering/renderers/verbose.ex

defmodule PromptRunner.Rendering.Renderers.VerboseRenderer do
@moduledoc """
Verbose line-by-line renderer for prompt-runner canonical events.
"""
@behaviour PromptRunner.Rendering.Renderer
@red "\e[0;31m"
@green "\e[0;32m"
@blue "\e[0;34m"
@dim "\e[2m"
@nc "\e[0m"
@preview_limit 120
@result_limit 2000
@impl true
def init(opts) do
{:ok,
%{
color: Keyword.get(opts, :color, true),
in_text: false,
event_count: 0,
tool_count: 0
}}
end
@impl true
def render_event(%{hidden?: true}, state), do: {:ok, [], state}
def render_event(event, state) do
state = %{state | event_count: state.event_count + 1}
{iodata, state} = render(event, state)
{:ok, iodata, state}
end
@impl true
def finish(state) do
closing = if state.in_text, do: "\n", else: ""
summary = dim("--- #{state.event_count} events, #{state.tool_count} tools ---\n", state)
{:ok, [closing, summary], state}
end
defp render(%{type: :run_started, data: data} = event, state) do
{break, state} = maybe_break_line(state)
model = data[:model] || "unknown"
session_id = event[:session_id] || data[:session_id] || ""
tag = colorize("[run_started]", @blue, state)
line = "#{tag} model=#{model} session_id=#{session_id}\n"
{[break, line], state}
end
defp render(%{type: :message_streamed, data: data}, state) do
text = data[:delta] || data[:content] || ""
{text, %{state | in_text: true}}
end
defp render(%{type: :tool_call_started, data: data}, state) do
{break, state} = maybe_break_line(state)
name = data[:tool_name] || "unknown"
id = data[:tool_call_id] || ""
input = format_tool_input(data[:tool_input])
tag = colorize("[tool_call_started]", @green, state)
line = "#{tag} name=#{name} id=#{id}#{input}\n"
state = %{state | tool_count: state.tool_count + 1}
{[break, line], state}
end
defp render(%{type: :tool_call_completed, data: data}, state) do
{break, state} = maybe_break_line(state)
name = data[:tool_name] || "unknown"
output = format_output(data[:tool_output])
tag = colorize("[tool_call_completed]", @green, state)
line = "#{tag} name=#{name}#{output}\n"
{[break, line], state}
end
defp render(%{type: :tool_call_failed, data: data}, state) do
{break, state} = maybe_break_line(state)
name = data[:tool_name] || "unknown"
output = format_output(data[:tool_output])
tag = colorize("[tool_call_failed]", @red, state)
line = "#{tag} name=#{name}#{output}\n"
{[break, line], state}
end
defp render(%{type: :token_usage_updated, data: data}, state) do
{break, state} = maybe_break_line(state)
input_t = data[:input_tokens] || 0
output_t = data[:output_tokens] || 0
cost_label = format_verbose_cost(data[:cost_usd])
tag = dim("[token_usage]", state)
line = "#{tag} input=#{input_t} output=#{output_t}#{cost_label}\n"
{[break, line], state}
end
defp render(%{type: :message_received, data: data}, state) do
{break, state} = maybe_break_line(state)
content = truncate(data[:content] || "", @result_limit)
role = data[:role] || "assistant"
tag = colorize("[message_received]", @blue, state)
line = "#{tag} role=#{role} content=#{content}\n"
{[break, line], %{state | in_text: false}}
end
defp render(%{type: :run_completed, data: data}, state) do
{break, state} = maybe_break_line(state)
reason = data[:stop_reason] || "end_turn"
usage = format_usage(data[:token_usage])
cost_label = format_verbose_cost(data[:cost_usd])
tag = colorize("[run_completed]", @blue, state)
line = "#{tag} stop_reason=#{reason}#{usage}#{cost_label}\n"
{[break, line], %{state | in_text: false}}
end
defp render(%{type: :run_failed, data: data}, state) do
{break, state} = maybe_break_line(state)
code = data[:error_code] || "unknown"
msg = data[:error_message] || "unknown error"
tag = colorize("[run_failed]", @red, state)
line = "#{tag} error_code=#{code} error_message=#{truncate(msg, @result_limit)}\n"
{[break, line], state}
end
defp render(%{type: :run_cancelled}, state) do
{break, state} = maybe_break_line(state)
tag = colorize("[run_cancelled]", @red, state)
{[break, "#{tag}\n"], state}
end
defp render(%{type: :error_occurred, data: data}, state) do
{break, state} = maybe_break_line(state)
code = data[:error_code] || "unknown"
msg = data[:error_message] || "unknown error"
tag = colorize("[error]", @red, state)
line = "#{tag} error_code=#{code} error_message=#{truncate(msg, @result_limit)}\n"
{[break, line], state}
end
defp render(%{type: type} = event, state) do
{break, state} = maybe_break_line(state)
data_preview = truncate(inspect(event[:data] || %{}), @preview_limit)
tag = dim("[event]", state)
line = "#{tag} type=#{type} data=#{data_preview}\n"
{[break, line], state}
end
defp maybe_break_line(%{in_text: true} = state), do: {"\n", %{state | in_text: false}}
defp maybe_break_line(state), do: {"", state}
defp colorize(text, color, %{color: true}), do: color <> text <> @nc
defp colorize(text, _color, _state), do: text
defp dim(text, %{color: true}), do: @dim <> text <> @nc
defp dim(text, _state), do: text
defp format_tool_input(nil), do: ""
defp format_tool_input(input) when input == %{}, do: ""
defp format_tool_input(input) when is_map(input) do
case Jason.encode(input) do
{:ok, json} -> " input=#{truncate(json, @preview_limit)}"
_ -> " input=#{truncate(inspect(input), @preview_limit)}"
end
end
defp format_tool_input(input), do: " input=#{truncate(inspect(input), @preview_limit)}"
defp format_output(nil), do: ""
defp format_output(""), do: ""
defp format_output(output), do: " output=#{truncate(to_string(output), @result_limit)}"
defp format_usage(nil), do: ""
defp format_usage(%{} = usage) do
input_t = usage[:input_tokens] || usage["input_tokens"] || 0
output_t = usage[:output_tokens] || usage["output_tokens"] || 0
" tokens=#{input_t}/#{output_t}"
end
defp format_usage(_), do: ""
defp format_verbose_cost(nil), do: ""
defp format_verbose_cost(cost) when is_number(cost), do: " cost=$#{Float.round(cost * 1.0, 6)}"
defp truncate(nil, _limit), do: ""
defp truncate(text, limit) when is_binary(text) do
if String.length(text) > limit do
String.slice(text, 0, limit) <> "..."
else
text
end
end
defp truncate(value, limit), do: truncate(inspect(value), limit)
end