Packages
A well-typed, idiomatic Gleam client for Anthropic's Claude API with streaming support and tool use
Current section
Files
Jump to
Current section
Files
src/anthropic@streaming@accumulator.erl
-module(anthropic@streaming@accumulator).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/anthropic/streaming/accumulator.gleam").
-export([new/0, process_event/2, process_events/1, process_events_with_callback/2, build_response/1, accumulate/1, get_accumulated_text/1, get_accumulated_tool_inputs/1, has_content/1, has_error/1, total_tokens/1]).
-export_type([accumulator_state/0, content_block_state/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(
" Stream accumulator for building complete messages from streaming events\n"
"\n"
" This module provides utilities for accumulating streaming events into\n"
" a complete message response. It tracks content blocks, handles deltas,\n"
" and produces the final message with usage statistics.\n"
).
-type accumulator_state() :: {accumulator_state,
gleam@option:option(binary()),
gleam@option:option(binary()),
gleam@option:option(anthropic@message:role()),
gleam@option:option(binary()),
gleam@dict:dict(integer(), content_block_state()),
gleam@option:option(anthropic@request:stop_reason()),
gleam@option:option(binary()),
integer(),
integer(),
boolean(),
gleam@option:option(anthropic@streaming:stream_error())}.
-type content_block_state() :: {text_block_state, binary()} |
{tool_use_block_state, binary(), binary(), binary()}.
-file("src/anthropic/streaming/accumulator.gleam", 69).
?DOC(" Create a new accumulator state\n").
-spec new() -> accumulator_state().
new() ->
{accumulator_state,
none,
none,
none,
none,
maps:new(),
none,
none,
0,
0,
false,
none}.
-file("src/anthropic/streaming/accumulator.gleam", 125).
?DOC(" Handle message_start event\n").
-spec handle_message_start(
accumulator_state(),
anthropic@streaming:message_start()
) -> accumulator_state().
handle_message_start(State, Msg) ->
{accumulator_state,
{some, erlang:element(2, Msg)},
{some, erlang:element(3, Msg)},
{some, erlang:element(4, Msg)},
{some, erlang:element(5, Msg)},
erlang:element(6, State),
erlang:element(7, State),
erlang:element(8, State),
erlang:element(2, erlang:element(6, Msg)),
erlang:element(10, State),
erlang:element(11, State),
erlang:element(12, State)}.
-file("src/anthropic/streaming/accumulator.gleam", 140).
?DOC(" Handle content_block_start event\n").
-spec handle_content_block_start(
accumulator_state(),
anthropic@streaming:content_block_start()
) -> accumulator_state().
handle_content_block_start(State, Start) ->
Block_state = case erlang:element(3, Start) of
{text_block, Text} ->
{text_block_state, Text};
{tool_use_block, Id, Name, Input} ->
{tool_use_block_state, Id, Name, Input};
_ ->
{text_block_state, <<""/utf8>>}
end,
{accumulator_state,
erlang:element(2, State),
erlang:element(3, State),
erlang:element(4, State),
erlang:element(5, State),
gleam@dict:insert(
erlang:element(6, State),
erlang:element(2, Start),
Block_state
),
erlang:element(7, State),
erlang:element(8, State),
erlang:element(9, State),
erlang:element(10, State),
erlang:element(11, State),
erlang:element(12, State)}.
-file("src/anthropic/streaming/accumulator.gleam", 182).
?DOC(" Apply a delta to a content block state\n").
-spec apply_delta(
content_block_state(),
anthropic@streaming:content_block_delta()
) -> content_block_state().
apply_delta(Block_state, Delta) ->
case {Block_state, Delta} of
{{text_block_state, Text}, {text_content_delta, Text_delta}} ->
{text_block_state,
<<Text/binary, (erlang:element(2, Text_delta))/binary>>};
{{tool_use_block_state, Id, Name, Input},
{input_json_content_delta, Json_delta}} ->
{tool_use_block_state,
Id,
Name,
<<Input/binary, (erlang:element(2, Json_delta))/binary>>};
{_, _} ->
Block_state
end.
-file("src/anthropic/streaming/accumulator.gleam", 158).
?DOC(" Handle content_block_delta event\n").
-spec handle_content_block_delta(
accumulator_state(),
anthropic@streaming:content_block_delta_event()
) -> accumulator_state().
handle_content_block_delta(State, Delta) ->
Updated_blocks = case gleam_stdlib:map_get(
erlang:element(6, State),
erlang:element(2, Delta)
) of
{ok, Block_state} ->
New_block_state = apply_delta(Block_state, erlang:element(3, Delta)),
gleam@dict:insert(
erlang:element(6, State),
erlang:element(2, Delta),
New_block_state
);
{error, _} ->
New_block_state@1 = case erlang:element(3, Delta) of
{text_content_delta, Text_delta} ->
{text_block_state, erlang:element(2, Text_delta)};
{input_json_content_delta, Json_delta} ->
{tool_use_block_state,
<<""/utf8>>,
<<""/utf8>>,
erlang:element(2, Json_delta)}
end,
gleam@dict:insert(
erlang:element(6, State),
erlang:element(2, Delta),
New_block_state@1
)
end,
{accumulator_state,
erlang:element(2, State),
erlang:element(3, State),
erlang:element(4, State),
erlang:element(5, State),
Updated_blocks,
erlang:element(7, State),
erlang:element(8, State),
erlang:element(9, State),
erlang:element(10, State),
erlang:element(11, State),
erlang:element(12, State)}.
-file("src/anthropic/streaming/accumulator.gleam", 200).
?DOC(" Handle message_delta event\n").
-spec handle_message_delta(
accumulator_state(),
anthropic@streaming:message_delta_event()
) -> accumulator_state().
handle_message_delta(State, Delta) ->
{accumulator_state,
erlang:element(2, State),
erlang:element(3, State),
erlang:element(4, State),
erlang:element(5, State),
erlang:element(6, State),
erlang:element(2, erlang:element(2, Delta)),
erlang:element(3, erlang:element(2, Delta)),
erlang:element(9, State),
erlang:element(2, erlang:element(3, Delta)),
erlang:element(11, State),
erlang:element(12, State)}.
-file("src/anthropic/streaming/accumulator.gleam", 86).
?DOC(" Process a single event and update accumulator state\n").
-spec process_event(accumulator_state(), anthropic@streaming:stream_event()) -> accumulator_state().
process_event(State, Event) ->
case Event of
{message_start_event, Msg} ->
handle_message_start(State, Msg);
{content_block_start_event, Start} ->
handle_content_block_start(State, Start);
{content_block_delta_event_variant, Delta} ->
handle_content_block_delta(State, Delta);
{content_block_stop_event, _} ->
State;
{message_delta_event_variant, Delta@1} ->
handle_message_delta(State, Delta@1);
message_stop_event ->
{accumulator_state,
erlang:element(2, State),
erlang:element(3, State),
erlang:element(4, State),
erlang:element(5, State),
erlang:element(6, State),
erlang:element(7, State),
erlang:element(8, State),
erlang:element(9, State),
erlang:element(10, State),
true,
erlang:element(12, State)};
ping_event ->
State;
{error_event, Err} ->
{accumulator_state,
erlang:element(2, State),
erlang:element(3, State),
erlang:element(4, State),
erlang:element(5, State),
erlang:element(6, State),
erlang:element(7, State),
erlang:element(8, State),
erlang:element(9, State),
erlang:element(10, State),
erlang:element(11, State),
{some, Err}}
end.
-file("src/anthropic/streaming/accumulator.gleam", 104).
?DOC(" Process multiple events and return final state\n").
-spec process_events(list(anthropic@streaming:stream_event())) -> accumulator_state().
process_events(Events) ->
gleam@list:fold(Events, new(), fun process_event/2).
-file("src/anthropic/streaming/accumulator.gleam", 109).
?DOC(" Process events with a callback for each event\n").
-spec process_events_with_callback(
list(anthropic@streaming:stream_event()),
fun((anthropic@streaming:stream_event(), accumulator_state()) -> nil)
) -> accumulator_state().
process_events_with_callback(Events, Callback) ->
gleam@list:fold(
Events,
new(),
fun(State, Event) ->
New_state = process_event(State, Event),
Callback(Event, New_state),
New_state
end
).
-file("src/anthropic/streaming/accumulator.gleam", 242).
?DOC(" Build content blocks from accumulated state\n").
-spec build_content_blocks(gleam@dict:dict(integer(), content_block_state())) -> list(anthropic@message:content_block()).
build_content_blocks(Blocks) ->
_pipe = Blocks,
_pipe@1 = maps:to_list(_pipe),
_pipe@2 = gleam@list:sort(
_pipe@1,
fun(A, B) ->
{Index_a, _} = A,
{Index_b, _} = B,
case Index_a < Index_b of
true ->
lt;
false ->
case Index_a > Index_b of
true ->
gt;
false ->
eq
end
end
end
),
gleam@list:map(
_pipe@2,
fun(Pair) ->
{_, Block_state} = Pair,
case Block_state of
{text_block_state, Text} ->
{text_block, Text};
{tool_use_block_state, Id, Name, Input} ->
{tool_use_block, Id, Name, Input}
end
end
).
-file("src/anthropic/streaming/accumulator.gleam", 270).
?DOC(" Helper for requiring Option values with use syntax\n").
-spec require_some(
gleam@option:option(ICB),
binary(),
fun((ICB) -> {ok, ICD} | {error, binary()})
) -> {ok, ICD} | {error, binary()}.
require_some(Opt, Error_msg, Next) ->
case Opt of
{some, Value} ->
Next(Value);
none ->
{error, Error_msg}
end.
-file("src/anthropic/streaming/accumulator.gleam", 217).
?DOC(" Build a CreateMessageResponse from the accumulated state\n").
-spec build_response(accumulator_state()) -> {ok,
anthropic@request:create_message_response()} |
{error, binary()}.
build_response(State) ->
require_some(
erlang:element(2, State),
<<"Missing message ID"/utf8>>,
fun(Id) ->
require_some(
erlang:element(4, State),
<<"Missing role"/utf8>>,
fun(Role) ->
require_some(
erlang:element(5, State),
<<"Missing model"/utf8>>,
fun(Model) ->
Content = build_content_blocks(
erlang:element(6, State)
),
{ok,
{create_message_response,
Id,
gleam@option:unwrap(
erlang:element(3, State),
<<"message"/utf8>>
),
Role,
Content,
Model,
erlang:element(7, State),
erlang:element(8, State),
{usage,
erlang:element(9, State),
erlang:element(10, State)}}}
end
)
end
)
end
).
-file("src/anthropic/streaming/accumulator.gleam", 286).
?DOC(" Accumulate events and build final response\n").
-spec accumulate(list(anthropic@streaming:stream_event())) -> {ok,
anthropic@request:create_message_response()} |
{error, binary()}.
accumulate(Events) ->
State = process_events(Events),
build_response(State).
-file("src/anthropic/streaming/accumulator.gleam", 294).
?DOC(" Get the accumulated text so far\n").
-spec get_accumulated_text(accumulator_state()) -> binary().
get_accumulated_text(State) ->
_pipe = erlang:element(6, State),
_pipe@1 = maps:values(_pipe),
_pipe@2 = gleam@list:filter_map(_pipe@1, fun(Block) -> case Block of
{text_block_state, Text} ->
{ok, Text};
_ ->
{error, nil}
end end),
gleam@string:join(_pipe@2, <<""/utf8>>).
-file("src/anthropic/streaming/accumulator.gleam", 307).
?DOC(" Get the accumulated tool inputs so far\n").
-spec get_accumulated_tool_inputs(accumulator_state()) -> list({binary(),
binary(),
binary()}).
get_accumulated_tool_inputs(State) ->
_pipe = erlang:element(6, State),
_pipe@1 = maps:values(_pipe),
gleam@list:filter_map(_pipe@1, fun(Block) -> case Block of
{tool_use_block_state, Id, Name, Input} ->
{ok, {Id, Name, Input}};
_ ->
{error, nil}
end end).
-file("src/anthropic/streaming/accumulator.gleam", 321).
?DOC(" Check if accumulator has any content\n").
-spec has_content(accumulator_state()) -> boolean().
has_content(State) ->
maps:size(erlang:element(6, State)) > 0.
-file("src/anthropic/streaming/accumulator.gleam", 326).
?DOC(" Check if accumulator encountered an error\n").
-spec has_error(accumulator_state()) -> boolean().
has_error(State) ->
gleam@option:is_some(erlang:element(12, State)).
-file("src/anthropic/streaming/accumulator.gleam", 331).
?DOC(" Get total token count\n").
-spec total_tokens(accumulator_state()) -> integer().
total_tokens(State) ->
erlang:element(9, State) + erlang:element(10, State).