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@client.erl
-module(anthropic@client).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/anthropic/client.gleam").
-export([new/1, handle_response/1, post_json/3, post_and_handle/3]).
-export_type([client/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(
" HTTP client wrapper for Anthropic API calls\n"
"\n"
" This module provides a client for making requests to the Anthropic Messages API,\n"
" handling headers, timeouts, and response parsing.\n"
).
-type client() :: {client, anthropic@config:config()}.
-file("src/anthropic/client.gleam", 45).
?DOC(" Create a new client from configuration\n").
-spec new(anthropic@config:config()) -> client().
new(Config) ->
{client, Config}.
-file("src/anthropic/client.gleam", 102).
?DOC(" Convert ConnectError to string\n").
-spec connect_error_to_string(gleam@httpc:connect_error()) -> binary().
connect_error_to_string(Err) ->
case Err of
{posix, Code} ->
<<"POSIX error: "/utf8, Code/binary>>;
{tls_alert, Code@1, Detail} ->
<<<<<<"TLS alert "/utf8, Code@1/binary>>/binary, ": "/utf8>>/binary,
Detail/binary>>
end.
-file("src/anthropic/client.gleam", 86).
?DOC(" Convert httpc error to AnthropicError\n").
-spec http_error_to_anthropic_error(gleam@httpc:http_error()) -> anthropic@types@error:anthropic_error().
http_error_to_anthropic_error(Err) ->
case Err of
invalid_utf8_response ->
anthropic@types@error:http_error(
<<"Invalid UTF-8 in response"/utf8>>
);
{failed_to_connect, Ip4, Ip6} ->
anthropic@types@error:network_error(
<<<<<<<<"Failed to connect to server (IPv4: "/utf8,
(connect_error_to_string(Ip4))/binary>>/binary,
", IPv6: "/utf8>>/binary,
(connect_error_to_string(Ip6))/binary>>/binary,
")"/utf8>>
);
response_timeout ->
anthropic@types@error:timeout_error(0)
end.
-file("src/anthropic/client.gleam", 174).
?DOC(" Error details decoder\n").
-spec error_details_decoder() -> gleam@dynamic@decode:decoder({anthropic@types@error:api_error_type(),
binary(),
gleam@option:option(binary()),
gleam@option:option(binary())}).
error_details_decoder() ->
gleam@dynamic@decode:field(
<<"type"/utf8>>,
{decoder, fun gleam@dynamic@decode:decode_string/1},
fun(Error_type_str) ->
gleam@dynamic@decode:field(
<<"message"/utf8>>,
{decoder, fun gleam@dynamic@decode:decode_string/1},
fun(Message) ->
gleam@dynamic@decode:optional_field(
<<"param"/utf8>>,
none,
begin
_pipe = {decoder,
fun gleam@dynamic@decode:decode_string/1},
gleam@dynamic@decode:map(
_pipe,
fun(Field@0) -> {some, Field@0} end
)
end,
fun(Param) ->
gleam@dynamic@decode:optional_field(
<<"code"/utf8>>,
none,
begin
_pipe@1 = {decoder,
fun gleam@dynamic@decode:decode_string/1},
gleam@dynamic@decode:map(
_pipe@1,
fun(Field@0) -> {some, Field@0} end
)
end,
fun(Code) ->
Error_type = anthropic@types@error:api_error_type_from_string(
Error_type_str
),
gleam@dynamic@decode:success(
{Error_type, Message, Param, Code}
)
end
)
end
)
end
)
end
).
-file("src/anthropic/client.gleam", 195).
?DOC(" Error wrapper decoder\n").
-spec error_wrapper_decoder() -> gleam@dynamic@decode:decoder({anthropic@types@error:api_error_type(),
binary(),
gleam@option:option(binary()),
gleam@option:option(binary())}).
error_wrapper_decoder() ->
gleam@dynamic@decode:field(
<<"error"/utf8>>,
error_details_decoder(),
fun(Details) -> gleam@dynamic@decode:success(Details) end
).
-file("src/anthropic/client.gleam", 203).
?DOC(" Parse error body JSON\n").
-spec parse_error_body(binary()) -> {ok,
{anthropic@types@error:api_error_type(),
binary(),
gleam@option:option(binary()),
gleam@option:option(binary())}} |
{error, nil}.
parse_error_body(Body) ->
case gleam_json_ffi:decode(Body) of
{ok, Dyn} ->
case gleam@dynamic@decode:run(Dyn, error_wrapper_decoder()) of
{ok, Details} ->
{ok, Details};
{error, _} ->
{error, nil}
end;
{error, _} ->
{error, nil}
end.
-file("src/anthropic/client.gleam", 151).
?DOC(" Parse an error response body into ApiError\n").
-spec parse_api_error(
integer(),
binary(),
anthropic@types@error:api_error_type()
) -> anthropic@types@error:anthropic_error().
parse_api_error(Status_code, Body, Default_type) ->
case parse_error_body(Body) of
{ok, {Error_type, Message, Param, Code}} ->
anthropic@types@error:api_error(
Status_code,
anthropic@types@error:api_error_details_full(
Error_type,
Message,
Param,
Code
)
);
{error, _} ->
anthropic@types@error:api_error(
Status_code,
anthropic@types@error:api_error_details(Default_type, Body)
)
end.
-file("src/anthropic/client.gleam", 114).
?DOC(" Handle an API response, parsing success or error\n").
-spec handle_response(gleam@http@response:response(binary())) -> {ok, binary()} |
{error, anthropic@types@error:anthropic_error()}.
handle_response(Response) ->
Status = erlang:element(2, Response),
case Status of
200 ->
{ok, erlang:element(4, Response)};
400 ->
{error,
parse_api_error(
Status,
erlang:element(4, Response),
invalid_request_error
)};
401 ->
{error,
parse_api_error(
Status,
erlang:element(4, Response),
authentication_error
)};
403 ->
{error,
parse_api_error(
Status,
erlang:element(4, Response),
permission_error
)};
404 ->
{error,
parse_api_error(
Status,
erlang:element(4, Response),
not_found_error
)};
429 ->
{error,
parse_api_error(
Status,
erlang:element(4, Response),
rate_limit_error
)};
500 ->
{error,
parse_api_error(
Status,
erlang:element(4, Response),
internal_api_error
)};
529 ->
{error,
parse_api_error(
Status,
erlang:element(4, Response),
overloaded_error
)};
_ when (Status >= 400) andalso (Status < 500) ->
{error,
parse_api_error(
Status,
erlang:element(4, Response),
invalid_request_error
)};
_ when Status >= 500 ->
{error,
parse_api_error(
Status,
erlang:element(4, Response),
internal_api_error
)};
_ ->
{error,
anthropic@types@error:http_error(
<<"Unexpected status code: "/utf8,
(gleam@string:inspect(Status))/binary>>
)}
end.
-file("src/anthropic/client.gleam", 54).
?DOC(" Make a POST request with JSON body to the specified path\n").
-spec post_json(client(), binary(), binary()) -> {ok,
gleam@http@response:response(binary())} |
{error, anthropic@types@error:anthropic_error()}.
post_json(Client, Path, Body) ->
Base_url = erlang:element(3, erlang:element(2, Client)),
Full_url = <<Base_url/binary, Path/binary>>,
gleam@result:'try'(
begin
_pipe = gleam@http@request:to(Full_url),
gleam@result:map_error(
_pipe,
fun(_) ->
anthropic@types@error:config_error(
<<"Invalid URL: "/utf8, Full_url/binary>>
)
end
)
end,
fun(Req) ->
Req@1 = begin
_pipe@1 = Req,
_pipe@2 = gleam@http@request:set_method(_pipe@1, post),
_pipe@3 = gleam@http@request:set_header(
_pipe@2,
<<"content-type"/utf8>>,
<<"application/json"/utf8>>
),
_pipe@4 = gleam@http@request:set_header(
_pipe@3,
<<"x-api-key"/utf8>>,
erlang:element(2, erlang:element(2, Client))
),
_pipe@5 = gleam@http@request:set_header(
_pipe@4,
<<"anthropic-version"/utf8>>,
<<"2023-06-01"/utf8>>
),
gleam@http@request:set_body(_pipe@5, Body)
end,
_pipe@6 = gleam@httpc:send(Req@1),
gleam@result:map_error(
_pipe@6,
fun(Err) -> http_error_to_anthropic_error(Err) end
)
end
).
-file("src/anthropic/client.gleam", 225).
?DOC(" Make a POST request and handle the response\n").
-spec post_and_handle(client(), binary(), binary()) -> {ok, binary()} |
{error, anthropic@types@error:anthropic_error()}.
post_and_handle(Client, Path, Body) ->
gleam@result:'try'(
post_json(Client, Path, Body),
fun(Response) -> handle_response(Response) end
).