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@http.erl
-module(anthropic@http).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/anthropic/http.gleam").
-export([method_to_string/1, check_status/1, parse_response_body/1, parse_messages_response/1, validate_request/1, build_messages_request/3, build_streaming_request/3]).
-export_type([method/0, http_request/0, http_response/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 types and request/response builders for sans-io pattern\n"
"\n"
" This module provides HTTP-library-agnostic types for building requests\n"
" and parsing responses. Users can use any HTTP client by:\n"
"\n"
" 1. Building a request with `build_messages_request`\n"
" 2. Sending it with their preferred HTTP client\n"
" 3. Parsing the response with `parse_messages_response`\n"
"\n"
" ## Example with custom HTTP client\n"
"\n"
" ```gleam\n"
" import anthropic/http\n"
" import anthropic/types/request\n"
" import anthropic/types/message.{user_message}\n"
"\n"
" // Build the request\n"
" let api_request = request.new(\n"
" \"claude-sonnet-4-20250514\",\n"
" [user_message(\"Hello!\")],\n"
" 1024,\n"
" )\n"
" let http_request = http.build_messages_request(config, api_request)\n"
"\n"
" // Send with your HTTP client (e.g., hackney, httpc, fetch on JS)\n"
" let http_response = my_http_client.send(http_request)\n"
"\n"
" // Parse the response\n"
" case http.parse_messages_response(http_response) {\n"
" Ok(response) -> io.println(request.response_text(response))\n"
" Error(err) -> io.println(error.error_to_string(err))\n"
" }\n"
" ```\n"
).
-type method() :: get | post | put | delete | patch.
-type http_request() :: {http_request,
method(),
binary(),
list({binary(), binary()}),
binary()}.
-type http_response() :: {http_response,
integer(),
list({binary(), binary()}),
binary()}.
-file("src/anthropic/http.gleam", 170).
?DOC(" Convert Method to string for HTTP libraries that need it\n").
-spec method_to_string(method()) -> binary().
method_to_string(Method) ->
case Method of
get ->
<<"GET"/utf8>>;
post ->
<<"POST"/utf8>>;
put ->
<<"PUT"/utf8>>;
delete ->
<<"DELETE"/utf8>>;
patch ->
<<"PATCH"/utf8>>
end.
-file("src/anthropic/http.gleam", 210).
?DOC(" Check HTTP status and extract body or error\n").
-spec check_status(http_response()) -> {ok, binary()} |
{error, anthropic@error:anthropic_error()}.
check_status(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/http.gleam", 250).
?DOC(" Parse successful response body into CreateMessageResponse\n").
-spec parse_response_body(binary()) -> {ok,
anthropic@request:create_message_response()} |
{error, anthropic@error:anthropic_error()}.
parse_response_body(Body) ->
anthropic@internal@decoder:parse_response_body(Body).
-file("src/anthropic/http.gleam", 200).
?DOC(
" Parse an HTTP response into a CreateMessageResponse\n"
"\n"
" This function handles:\n"
" - Status code checking (success vs error)\n"
" - Error response parsing with proper error types\n"
" - Success response JSON decoding\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" let http_response = HttpResponse(status: 200, headers: [], body: json_body)\n"
" case parse_messages_response(http_response) {\n"
" Ok(response) -> handle_success(response)\n"
" Error(err) -> handle_error(err)\n"
" }\n"
" ```\n"
).
-spec parse_messages_response(http_response()) -> {ok,
anthropic@request:create_message_response()} |
{error, anthropic@error:anthropic_error()}.
parse_messages_response(Response) ->
case check_status(Response) of
{ok, Body} ->
parse_response_body(Body);
{error, Err} ->
{error, Err}
end.
-file("src/anthropic/http.gleam", 264).
?DOC(
" Validate a CreateMessageRequest before sending\n"
"\n"
" This performs client-side validation to catch errors early.\n"
" Delegates to the shared validation module for consistent validation rules.\n"
).
-spec validate_request(anthropic@request:create_message_request()) -> {ok, nil} |
{error, anthropic@error:anthropic_error()}.
validate_request(Req) ->
anthropic@internal@validation:validate_request_or_error(Req).
-file("src/anthropic/http.gleam", 156).
?DOC(" Build standard Anthropic API headers\n").
-spec build_headers(binary(), boolean()) -> list({binary(), binary()}).
build_headers(Api_key, Streaming) ->
Base_headers = [{<<"content-type"/utf8>>, <<"application/json"/utf8>>},
{<<"x-api-key"/utf8>>, Api_key},
{<<"anthropic-version"/utf8>>, <<"2023-06-01"/utf8>>}],
case Streaming of
true ->
lists:append(
Base_headers,
[{<<"accept"/utf8>>, <<"text/event-stream"/utf8>>}]
);
false ->
Base_headers
end.
-file("src/anthropic/http.gleam", 118).
?DOC(
" Build an HTTP request for the Messages API\n"
"\n"
" This function creates an HTTP-library-agnostic request that can be\n"
" sent using any HTTP client. It handles:\n"
" - URL construction\n"
" - Authentication headers\n"
" - Content-Type and API version headers\n"
" - JSON body encoding\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" let req = request.new(\"claude-sonnet-4-20250514\", messages, 1024)\n"
" let http_req = build_messages_request(\"sk-ant-...\", default_base_url, req)\n"
" // Send http_req with your HTTP client\n"
" ```\n"
).
-spec build_messages_request(
binary(),
binary(),
anthropic@request:create_message_request()
) -> http_request().
build_messages_request(Api_key, Base_url, Message_request) ->
Url = <<Base_url/binary, "/v1/messages"/utf8>>,
Body = anthropic@request:request_to_json_string(Message_request),
{http_request, post, Url, build_headers(Api_key, false), Body}.
-file("src/anthropic/http.gleam", 138).
?DOC(
" Build an HTTP request for streaming Messages API\n"
"\n"
" Similar to `build_messages_request` but adds the Accept header for SSE\n"
" and ensures the stream flag is set on the request.\n"
).
-spec build_streaming_request(
binary(),
binary(),
anthropic@request:create_message_request()
) -> http_request().
build_streaming_request(Api_key, Base_url, Message_request) ->
Streaming_request = anthropic@request:with_stream(Message_request, true),
Url = <<Base_url/binary, "/v1/messages"/utf8>>,
Body = anthropic@request:request_to_json_string(Streaming_request),
{http_request, post, Url, build_headers(Api_key, true), Body}.