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, init/0, init_with_key/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"
"\n"
" ## Quick Start\n"
"\n"
" For most use cases, use the simple `init` functions:\n"
"\n"
" ```gleam\n"
" import anthropic/client\n"
" import anthropic/api\n"
" import anthropic/types/request\n"
" import anthropic/types/message.{user_message}\n"
"\n"
" // Read API key from ANTHROPIC_API_KEY environment variable\n"
" let assert Ok(client) = client.init()\n"
"\n"
" // Or provide the key explicitly\n"
" let assert Ok(client) = client.init_with_key(\"sk-ant-...\")\n"
"\n"
" // Create a request and chat\n"
" let req = request.new(\"claude-sonnet-4-20250514\", [user_message(\"Hello!\")], 1024)\n"
" api.chat(client, req)\n"
" ```\n"
"\n"
" ## Advanced Configuration\n"
"\n"
" For custom base URLs, timeouts, or retry settings, use the config builders:\n"
"\n"
" ```gleam\n"
" import anthropic/config\n"
" import anthropic/client\n"
"\n"
" let assert Ok(cfg) = config.config_options()\n"
" |> config.with_api_key(\"sk-ant-...\")\n"
" |> config.with_base_url(\"https://custom-endpoint.example.com\")\n"
" |> config.with_timeout_ms(120_000)\n"
" |> config.load_config()\n"
"\n"
" let client = client.new(cfg)\n"
" ```\n"
).
-type client() :: {client, anthropic@config:config()}.
-file("src/anthropic/client.gleam", 84).
?DOC(
" Create a new client from configuration\n"
"\n"
" For advanced use cases where you need custom configuration.\n"
" For simple use cases, prefer `init()` or `init_with_key()`.\n"
).
-spec new(anthropic@config:config()) -> client().
new(Config) ->
{client, Config}.
-file("src/anthropic/client.gleam", 100).
?DOC(
" Initialize a client using the ANTHROPIC_API_KEY environment variable\n"
"\n"
" This is the simplest way to create a client when you have your API key\n"
" set in the environment.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" // Ensure ANTHROPIC_API_KEY is set in your environment\n"
" let assert Ok(client) = client.init()\n"
" api.create_message(client, request)\n"
" ```\n"
).
-spec init() -> {ok, client()} | {error, anthropic@error:anthropic_error()}.
init() ->
_pipe = anthropic@config:config_options(),
_pipe@1 = anthropic@config:load_config(_pipe),
gleam@result:map(_pipe@1, fun new/1).
-file("src/anthropic/client.gleam", 117).
?DOC(
" Initialize a client with an explicit API key\n"
"\n"
" Use this when you want to provide the API key directly rather than\n"
" reading from environment variables.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" let assert Ok(client) = client.init_with_key(\"sk-ant-api03-...\")\n"
" api.create_message(client, request)\n"
" ```\n"
).
-spec init_with_key(binary()) -> {ok, client()} |
{error, anthropic@error:anthropic_error()}.
init_with_key(Api_key) ->
_pipe = anthropic@config:config_options(),
_pipe@1 = anthropic@config:with_api_key(_pipe, Api_key),
_pipe@2 = anthropic@config:load_config(_pipe@1),
gleam@result:map(_pipe@2, fun new/1).
-file("src/anthropic/client.gleam", 177).
?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", 161).
?DOC(" Convert httpc error to AnthropicError\n").
-spec http_error_to_anthropic_error(gleam@httpc:http_error()) -> anthropic@error:anthropic_error().
http_error_to_anthropic_error(Err) ->
case Err of
invalid_utf8_response ->
anthropic@error:http_error(<<"Invalid UTF-8 in response"/utf8>>);
{failed_to_connect, Ip4, Ip6} ->
anthropic@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@error:timeout_error(0)
end.
-file("src/anthropic/client.gleam", 189).
?DOC(" Handle an API response, parsing success or error\n").
-spec handle_response(gleam@http@response:response(binary())) -> {ok, binary()} |
{error, anthropic@error:anthropic_error()}.
handle_response(Response) ->
Status = erlang:element(2, Response),
case Status of
200 ->
{ok, erlang:element(4, Response)};
400 ->
{error,
anthropic@internal@decoder:parse_api_error(
Status,
erlang:element(4, Response),
invalid_request_error
)};
401 ->
{error,
anthropic@internal@decoder:parse_api_error(
Status,
erlang:element(4, Response),
authentication_error
)};
403 ->
{error,
anthropic@internal@decoder:parse_api_error(
Status,
erlang:element(4, Response),
permission_error
)};
404 ->
{error,
anthropic@internal@decoder:parse_api_error(
Status,
erlang:element(4, Response),
not_found_error
)};
429 ->
{error,
anthropic@internal@decoder:parse_api_error(
Status,
erlang:element(4, Response),
rate_limit_error
)};
500 ->
{error,
anthropic@internal@decoder:parse_api_error(
Status,
erlang:element(4, Response),
internal_api_error
)};
529 ->
{error,
anthropic@internal@decoder:parse_api_error(
Status,
erlang:element(4, Response),
overloaded_error
)};
_ when (Status >= 400) andalso (Status < 500) ->
{error,
anthropic@internal@decoder:parse_api_error(
Status,
erlang:element(4, Response),
invalid_request_error
)};
_ when Status >= 500 ->
{error,
anthropic@internal@decoder:parse_api_error(
Status,
erlang:element(4, Response),
internal_api_error
)};
_ ->
{error,
anthropic@error:http_error(
<<"Unexpected status code: "/utf8,
(gleam@string:inspect(Status))/binary>>
)}
end.
-file("src/anthropic/client.gleam", 129).
?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@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@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>>,
anthropic@config:api_key_to_string(
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", 235).
?DOC(" Make a POST request and handle the response\n").
-spec post_and_handle(client(), binary(), binary()) -> {ok, binary()} |
{error, anthropic@error:anthropic_error()}.
post_and_handle(Client, Path, Body) ->
gleam@result:'try'(
post_json(Client, Path, Body),
fun(Response) -> handle_response(Response) end
).