Packages

A well-typed, idiomatic Gleam client for Anthropic's Claude API with streaming support and tool use

Current section

Files

Jump to
anthropic_gleam src anthropic@api.erl
Raw

src/anthropic@api.erl

-module(anthropic@api).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/anthropic/api.gleam").
-export([create_message/2]).
-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(
" API functions for Anthropic Messages API\n"
"\n"
" This module provides the core functions for interacting with Claude's\n"
" Messages API, including message creation and response parsing.\n"
).
-file("src/anthropic/api.gleam", 69).
?DOC(" Validate a CreateMessageRequest before sending\n").
-spec validate_request(anthropic@types@request:create_message_request()) -> {ok,
nil} |
{error, anthropic@types@error:anthropic_error()}.
validate_request(Request) ->
_pipe = case gleam@list:is_empty(erlang:element(3, Request)) of
true ->
{error,
anthropic@types@error:invalid_request_error(
<<"messages list cannot be empty"/utf8>>
)};
false ->
{ok, nil}
end,
_pipe@1 = gleam@result:'try'(
_pipe,
fun(_) ->
case gleam@string:is_empty(
gleam@string:trim(erlang:element(2, Request))
) of
true ->
{error,
anthropic@types@error:invalid_request_error(
<<"model name cannot be empty"/utf8>>
)};
false ->
{ok, nil}
end
end
),
gleam@result:'try'(_pipe@1, fun(_) -> case erlang:element(4, Request) > 0 of
true ->
{ok, nil};
false ->
{error,
anthropic@types@error:invalid_request_error(
<<"max_tokens must be greater than 0"/utf8>>
)}
end end).
-file("src/anthropic/api.gleam", 125).
?DOC(" Convert decode errors to a string\n").
-spec decode_errors_to_string(list(gleam@dynamic@decode:decode_error())) -> binary().
decode_errors_to_string(Errors) ->
_pipe = Errors,
_pipe@1 = gleam@list:map(
_pipe,
fun(E) ->
<<<<<<"expected "/utf8, (erlang:element(2, E))/binary>>/binary,
", got "/utf8>>/binary,
(erlang:element(3, E))/binary>>
end
),
gleam@string:join(_pipe@1, <<"; "/utf8>>).
-file("src/anthropic/api.gleam", 184).
?DOC(" Decoder for text blocks\n").
-spec text_block_decoder() -> gleam@dynamic@decode:decoder(anthropic@types@message:content_block()).
text_block_decoder() ->
gleam@dynamic@decode:field(
<<"text"/utf8>>,
{decoder, fun gleam@dynamic@decode:decode_string/1},
fun(Text) -> gleam@dynamic@decode:success({text_block, Text}) end
).
-file("src/anthropic/api.gleam", 209).
?DOC(" Convert a dynamic value to a JSON string using Erlang's built-in json module\n").
-spec dynamic_to_json_string(gleam@dynamic:dynamic_()) -> binary().
dynamic_to_json_string(Value) ->
Iodata = json:encode(Value),
erlang:iolist_to_binary(Iodata).
-file("src/anthropic/api.gleam", 200).
?DOC(" Decoder for tool input (converts dynamic to JSON string)\n").
-spec input_decoder() -> gleam@dynamic@decode:decoder(binary()).
input_decoder() ->
gleam@dynamic@decode:new_primitive_decoder(
<<"Object"/utf8>>,
fun(Data) ->
Json_str = dynamic_to_json_string(Data),
{ok, Json_str}
end
).
-file("src/anthropic/api.gleam", 190).
?DOC(" Decoder for tool use blocks\n").
-spec tool_use_block_decoder() -> gleam@dynamic@decode:decoder(anthropic@types@message:content_block()).
tool_use_block_decoder() ->
gleam@dynamic@decode:field(
<<"id"/utf8>>,
{decoder, fun gleam@dynamic@decode:decode_string/1},
fun(Id) ->
gleam@dynamic@decode:field(
<<"name"/utf8>>,
{decoder, fun gleam@dynamic@decode:decode_string/1},
fun(Name) ->
gleam@dynamic@decode:field(
<<"input"/utf8>>,
input_decoder(),
fun(Input) ->
gleam@dynamic@decode:success(
{tool_use_block, Id, Name, Input}
)
end
)
end
)
end
).
-file("src/anthropic/api.gleam", 169).
?DOC(" Decoder for ContentBlock\n").
-spec content_block_decoder() -> gleam@dynamic@decode:decoder(anthropic@types@message:content_block()).
content_block_decoder() ->
gleam@dynamic@decode:field(
<<"type"/utf8>>,
{decoder, fun gleam@dynamic@decode:decode_string/1},
fun(Block_type) -> case Block_type of
<<"text"/utf8>> ->
text_block_decoder();
<<"tool_use"/utf8>> ->
tool_use_block_decoder();
_ ->
gleam@dynamic@decode:success(
{text_block,
<<<<"[Unknown content type: "/utf8,
Block_type/binary>>/binary,
"]"/utf8>>}
)
end end
).
-file("src/anthropic/api.gleam", 224).
?DOC(" Decoder for Usage\n").
-spec usage_decoder() -> gleam@dynamic@decode:decoder(anthropic@types@request:usage()).
usage_decoder() ->
gleam@dynamic@decode:field(
<<"input_tokens"/utf8>>,
{decoder, fun gleam@dynamic@decode:decode_int/1},
fun(Input_tokens) ->
gleam@dynamic@decode:field(
<<"output_tokens"/utf8>>,
{decoder, fun gleam@dynamic@decode:decode_int/1},
fun(Output_tokens) ->
gleam@dynamic@decode:success(
{usage, Input_tokens, Output_tokens}
)
end
)
end
).
-file("src/anthropic/api.gleam", 235).
?DOC(" Parse a role string\n").
-spec parse_role(binary()) -> anthropic@types@message:role().
parse_role(Str) ->
case Str of
<<"user"/utf8>> ->
user;
<<"assistant"/utf8>> ->
assistant;
_ ->
assistant
end.
-file("src/anthropic/api.gleam", 244).
?DOC(" Parse a stop reason string\n").
-spec parse_stop_reason(binary()) -> gleam@option:option(anthropic@types@request:stop_reason()).
parse_stop_reason(Str) ->
case Str of
<<"end_turn"/utf8>> ->
{some, end_turn};
<<"max_tokens"/utf8>> ->
{some, max_tokens};
<<"stop_sequence"/utf8>> ->
{some, stop_sequence};
<<"tool_use"/utf8>> ->
{some, tool_use};
_ ->
none
end.
-file("src/anthropic/api.gleam", 132).
?DOC(" Decoder for CreateMessageResponse\n").
-spec response_decoder() -> gleam@dynamic@decode:decoder(anthropic@types@request:create_message_response()).
response_decoder() ->
gleam@dynamic@decode:field(
<<"id"/utf8>>,
{decoder, fun gleam@dynamic@decode:decode_string/1},
fun(Id) ->
gleam@dynamic@decode:field(
<<"type"/utf8>>,
{decoder, fun gleam@dynamic@decode:decode_string/1},
fun(Response_type) ->
gleam@dynamic@decode:field(
<<"role"/utf8>>,
{decoder, fun gleam@dynamic@decode:decode_string/1},
fun(Role_str) ->
gleam@dynamic@decode:field(
<<"content"/utf8>>,
gleam@dynamic@decode:list(
content_block_decoder()
),
fun(Content) ->
gleam@dynamic@decode:field(
<<"model"/utf8>>,
{decoder,
fun gleam@dynamic@decode:decode_string/1},
fun(Model) ->
gleam@dynamic@decode:field(
<<"usage"/utf8>>,
usage_decoder(),
fun(Usage) ->
gleam@dynamic@decode:field(
<<"stop_reason"/utf8>>,
begin
_pipe = gleam@dynamic@decode:optional(
{decoder,
fun gleam@dynamic@decode:decode_string/1}
),
gleam@dynamic@decode:map(
_pipe,
fun(Opt) ->
case Opt of
{some,
S} ->
parse_stop_reason(
S
);
none ->
none
end
end
)
end,
fun(Stop_reason) ->
gleam@dynamic@decode:field(
<<"stop_sequence"/utf8>>,
gleam@dynamic@decode:optional(
{decoder,
fun gleam@dynamic@decode:decode_string/1}
),
fun(
Stop_sequence
) ->
Role = parse_role(
Role_str
),
gleam@dynamic@decode:success(
{create_message_response,
Id,
Response_type,
Role,
Content,
Model,
Stop_reason,
Stop_sequence,
Usage}
)
end
)
end
)
end
)
end
)
end
)
end
)
end
)
end
).
-file("src/anthropic/api.gleam", 111).
?DOC(" Decode a dynamic value into CreateMessageResponse\n").
-spec decode_response(gleam@dynamic:dynamic_()) -> {ok,
anthropic@types@request:create_message_response()} |
{error, anthropic@types@error:anthropic_error()}.
decode_response(Value) ->
Decoder = response_decoder(),
case gleam@dynamic@decode:run(Value, Decoder) of
{ok, Response} ->
{ok, Response};
{error, Errors} ->
{error,
anthropic@types@error:json_error(
<<"Failed to decode response: "/utf8,
(decode_errors_to_string(Errors))/binary>>
)}
end.
-file("src/anthropic/api.gleam", 99).
?DOC(" Parse a response body into CreateMessageResponse\n").
-spec parse_response(binary()) -> {ok,
anthropic@types@request:create_message_response()} |
{error, anthropic@types@error:anthropic_error()}.
parse_response(Body) ->
case gleam_json_ffi:decode(Body) of
{ok, Dyn} ->
decode_response(Dyn);
{error, _} ->
{error,
anthropic@types@error:json_error(
<<"Failed to parse response JSON"/utf8>>
)}
end.
-file("src/anthropic/api.gleam", 43).
?DOC(
" Create a message using the Anthropic Messages API\n"
"\n"
" This function sends a request to Claude and returns the response.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" let request = create_request(\n"
" \"claude-sonnet-4-20250514\",\n"
" [user_message(\"Hello, Claude!\")],\n"
" 1024,\n"
" )\n"
" case create_message(client, request) {\n"
" Ok(response) -> io.println(response_text(response))\n"
" Error(err) -> io.println(error_to_string(err))\n"
" }\n"
" ```\n"
).
-spec create_message(
anthropic@client:client(),
anthropic@types@request:create_message_request()
) -> {ok, anthropic@types@request:create_message_response()} |
{error, anthropic@types@error:anthropic_error()}.
create_message(Client, Request) ->
gleam@result:'try'(
validate_request(Request),
fun(_) ->
Body = anthropic@types@request:request_to_json_string(Request),
gleam@result:'try'(
anthropic@client:post_and_handle(
Client,
<<"/v1/messages"/utf8>>,
Body
),
fun(Response_body) -> parse_response(Response_body) end
)
end
).