Packages

Elixir/OTP runtime for supervised coding-agent sessions with ACP, Subagents, Workflows, and replayable evidence.

Current section

Files

Jump to
pixir lib pixir providers anthropic replay.ex
Raw

lib/pixir/providers/anthropic/replay.ex

defmodule Pixir.Providers.Anthropic.Replay do
@moduledoc """
Pure replay helpers for Anthropic extended-thinking content.
`replayable_block/2` accepts the string-keyed `data` from one canonical
`:reasoning` Event and returns the captured raw Anthropic `thinking` or
`redacted_thinking` block only when the event is Anthropic-dialect and was
captured by the same model. Blocks are never normalized or rewritten; signatures
and all other fields remain byte-identical to the Log payload.
`assistant_content/3` is the P6 fold integration seam. It accepts one assistant
turn's ordered History items (reasoning Events interleaved with tool_call Events
in arrival order) and a renderer function for non-reasoning/tool positions. This
module deliberately does not know how canonical `tool_call` Events become
Anthropic `tool_use` blocks; P6 supplies that renderer. The renderer is called
with each non-replayable item and may return a block, a list of blocks, or `nil`.
"""
@type event_like :: %{optional(:type) => atom(), optional(:data) => map()}
@doc "Return a verbatim Anthropic thinking block when dialect and model match."
@spec replayable_block(map(), String.t()) :: {:ok, map()} | :drop
def replayable_block(%{"dialect" => "anthropic", "model" => model, "item" => item}, model)
when is_binary(model) and is_map(item) do
{:ok, item}
end
def replayable_block(_data, _model), do: :drop
@doc """
Build Anthropic assistant content, preserving reasoning/tool positions.
The renderer is required: non-reasoning items (tool_call events) must always
be transformed by the caller (the P6 fold) — a raw canonical event is never a
valid Anthropic content block. Envelope keys are atoms by contract, so only
atom-keyed `:reasoning` events are recognized.
"""
@spec assistant_content([event_like()], String.t(), (event_like() -> map() | [map()] | nil)) ::
[map()]
def assistant_content(items, model, render_tool_position)
when is_list(items) and is_function(render_tool_position, 1) do
Enum.flat_map(items, fn
%{type: :reasoning, data: data} ->
case replayable_block(data, model) do
{:ok, block} -> [block]
:drop -> []
end
item ->
item
|> render_tool_position.()
|> List.wrap()
|> Enum.reject(&is_nil/1)
end)
end
end