Packages

Gleam SDK for the Anthropic Claude API with agentic tool-use loop and BEAM concurrency

Current section

Files

Jump to
claude_gleam src claude.erl
Raw

src/claude.erl

-module(claude).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/claude.gleam").
-export([version/0, new/1, from_env/0, run/3, run_with_config/2, message/2, text_content/1, result_text/1]).
-export_type([env_error/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.
-type env_error() :: env_var_not_set | env_var_empty.
-file("src/claude.gleam", 22).
?DOC(" The current SDK version.\n").
-spec version() -> binary().
version() ->
<<"1.0.0"/utf8>>.
-file("src/claude.gleam", 38).
?DOC(
" Create a new client configuration with the given API key.\n"
"\n"
" Uses sensible defaults:\n"
" - base_url: \"https://api.anthropic.com\"\n"
" - model: \"claude-sonnet-4-5-20250929\"\n"
" - max_tokens: 4096\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" let client = claude.new(\"sk-ant-...\")\n"
" ```\n"
).
-spec new(binary()) -> claude@client:config().
new(Api_key) ->
claude@client:new(Api_key).
-file("src/claude.gleam", 52).
?DOC(
" Create a client configuration from the ANTHROPIC_API_KEY environment variable.\n"
"\n"
" Returns `Error(EnvVarNotSet)` if the environment variable is not set,\n"
" or `Error(EnvVarEmpty)` if it is set but empty.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" let assert Ok(client) = claude.from_env()\n"
" ```\n"
).
-spec from_env() -> {ok, claude@client:config()} | {error, env_error()}.
from_env() ->
case claude_ffi:get_env(<<"ANTHROPIC_API_KEY"/utf8>>) of
{ok, Key} ->
case gleam@string:is_empty(Key) of
true ->
{error, env_var_empty};
false ->
{ok, claude@client:new(Key)}
end;
{error, _} ->
{error, env_var_not_set}
end.
-file("src/claude.gleam", 86).
?DOC(
" Run an agent loop with the given tool registry.\n"
"\n"
" This is the primary entry point for running an agentic workflow.\n"
" The agent sends the prompt to the API, and if the model responds with\n"
" tool calls, it executes them using the typed tools in the registry and\n"
" feeds the results back. This continues until the model stops calling\n"
" tools or the maximum number of iterations is reached.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" let client = claude.new(\"sk-ant-...\")\n"
" let tools = tool.registry()\n"
" |> tool.register(weather_tool())\n"
"\n"
" case claude.run(client, \"What's the weather in SF?\", tools) {\n"
" Ok(result) -> io.println(claude.result_text(result))\n"
" Error(err) -> io.println(\"Error!\")\n"
" }\n"
" ```\n"
).
-spec run(claude@client:config(), binary(), claude@types@tool:registry()) -> {ok,
claude@agent:agent_result()} |
{error, claude@agent:agent_error()}.
run(Client, Prompt, Tools) ->
Cfg = claude@agent@config:new(Client, Tools),
claude@agent:run(Cfg, Prompt).
-file("src/claude.gleam", 117).
?DOC(
" Run an agent loop with a fully customized configuration.\n"
"\n"
" Use this when you need to set system prompts, custom models,\n"
" thinking budgets, or other advanced options.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" let tools = tool.registry()\n"
" |> tool.register(weather_tool())\n"
"\n"
" let cfg =\n"
" config.new(client: client, tools: tools)\n"
" |> config.with_system(\"You are a helpful assistant.\")\n"
" |> config.with_model(\"claude-opus-4-5-20250929\")\n"
" |> config.with_max_iterations(5)\n"
"\n"
" case claude.run_with_config(cfg, \"Analyze this data\") {\n"
" Ok(result) -> io.println(claude.result_text(result))\n"
" Error(_) -> io.println(\"Error!\")\n"
" }\n"
" ```\n"
).
-spec run_with_config(claude@agent@config:agent_config(), binary()) -> {ok,
claude@agent:agent_result()} |
{error, claude@agent:agent_error()}.
run_with_config(Config, Prompt) ->
claude@agent:run(Config, Prompt).
-file("src/claude.gleam", 138).
?DOC(
" Send a single message to the API without the agent loop.\n"
"\n"
" This is useful for simple one-shot interactions where tool use\n"
" is not needed.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" let client = claude.new(\"sk-ant-...\")\n"
" case claude.message(client, \"What is 2 + 2?\") {\n"
" Ok(msg) -> io.println(claude.text_content(msg))\n"
" Error(_) -> io.println(\"API error\")\n"
" }\n"
" ```\n"
).
-spec message(claude@client:config(), binary()) -> {ok,
claude@types@message:message()} |
{error, claude@types@error:api_error()}.
message(Client, Prompt) ->
claude@messages:create(
{request_params,
Client,
none,
none,
[claude@types@message:new_user(Prompt)],
claude@types@tool:registry(),
none,
none,
none}
).
-file("src/claude.gleam", 165).
?DOC(
" Extract and concatenate all text content blocks from a message.\n"
"\n"
" Tool use blocks, thinking blocks, and other non-text blocks are ignored.\n"
" Multiple text blocks are joined with newlines.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" let assert Ok(msg) = claude.message(client, \"Hello!\")\n"
" io.println(claude.text_content(msg))\n"
" ```\n"
).
-spec text_content(claude@types@message:message()) -> binary().
text_content(Msg) ->
_pipe = erlang:element(4, Msg),
_pipe@1 = gleam@list:filter_map(_pipe, fun(Block) -> case Block of
{text, T, _} ->
{ok, T};
_ ->
{error, nil}
end end),
gleam@string:join(_pipe@1, <<"\n"/utf8>>).
-file("src/claude.gleam", 187).
?DOC(
" Extract the text content from an agent result's final message.\n"
"\n"
" This is a convenience function that applies `text_content` to the\n"
" result's final message.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" let assert Ok(result) = claude.run(client, \"Hello\", tools)\n"
" io.println(claude.result_text(result))\n"
" ```\n"
).
-spec result_text(claude@agent:agent_result()) -> binary().
result_text(Result) ->
text_content(erlang:element(3, Result)).