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.gleam
Raw

src/claude.gleam

import claude/agent
import claude/agent/config.{type AgentConfig}
import claude/client
import claude/messages
import claude/types/content.{type ContentBlock, Text}
import claude/types/error.{type ApiError}
import claude/types/message.{type Message}
import claude/types/tool
import gleam/list
import gleam/option.{None}
import gleam/string
/// Error type for environment variable lookup failures.
pub type EnvError {
/// The ANTHROPIC_API_KEY environment variable is not set.
EnvVarNotSet
/// The ANTHROPIC_API_KEY environment variable is set but empty.
EnvVarEmpty
}
/// The current SDK version.
pub fn version() -> String {
"1.0.0"
}
/// Create a new client configuration with the given API key.
///
/// Uses sensible defaults:
/// - base_url: "https://api.anthropic.com"
/// - model: "claude-sonnet-4-5-20250929"
/// - max_tokens: 4096
///
/// ## Example
///
/// ```gleam
/// let client = claude.new("sk-ant-...")
/// ```
pub fn new(api_key: String) -> client.Config {
client.new(api_key)
}
/// Create a client configuration from the ANTHROPIC_API_KEY environment variable.
///
/// Returns `Error(EnvVarNotSet)` if the environment variable is not set,
/// or `Error(EnvVarEmpty)` if it is set but empty.
///
/// ## Example
///
/// ```gleam
/// let assert Ok(client) = claude.from_env()
/// ```
pub fn from_env() -> Result(client.Config, EnvError) {
case get_env("ANTHROPIC_API_KEY") {
Ok(key) ->
case string.is_empty(key) {
True -> Error(EnvVarEmpty)
False -> Ok(client.new(key))
}
Error(_) -> Error(EnvVarNotSet)
}
}
@external(erlang, "claude_ffi", "get_env")
fn get_env(name: String) -> Result(String, Nil)
/// Run an agent loop with the given tool registry.
///
/// This is the primary entry point for running an agentic workflow.
/// The agent sends the prompt to the API, and if the model responds with
/// tool calls, it executes them using the typed tools in the registry and
/// feeds the results back. This continues until the model stops calling
/// tools or the maximum number of iterations is reached.
///
/// ## Example
///
/// ```gleam
/// let client = claude.new("sk-ant-...")
/// let tools = tool.registry()
/// |> tool.register(weather_tool())
///
/// case claude.run(client, "What's the weather in SF?", tools) {
/// Ok(result) -> io.println(claude.result_text(result))
/// Error(err) -> io.println("Error!")
/// }
/// ```
pub fn run(
client: client.Config,
prompt: String,
tools: tool.Registry,
) -> Result(agent.AgentResult, agent.AgentError) {
let cfg = config.new(client: client, tools: tools)
agent.run(cfg, prompt)
}
/// Run an agent loop with a fully customized configuration.
///
/// Use this when you need to set system prompts, custom models,
/// thinking budgets, or other advanced options.
///
/// ## Example
///
/// ```gleam
/// let tools = tool.registry()
/// |> tool.register(weather_tool())
///
/// let cfg =
/// config.new(client: client, tools: tools)
/// |> config.with_system("You are a helpful assistant.")
/// |> config.with_model("claude-opus-4-5-20250929")
/// |> config.with_max_iterations(5)
///
/// case claude.run_with_config(cfg, "Analyze this data") {
/// Ok(result) -> io.println(claude.result_text(result))
/// Error(_) -> io.println("Error!")
/// }
/// ```
pub fn run_with_config(
config: AgentConfig,
prompt: String,
) -> Result(agent.AgentResult, agent.AgentError) {
agent.run(config, prompt)
}
/// Send a single message to the API without the agent loop.
///
/// This is useful for simple one-shot interactions where tool use
/// is not needed.
///
/// ## Example
///
/// ```gleam
/// let client = claude.new("sk-ant-...")
/// case claude.message(client, "What is 2 + 2?") {
/// Ok(msg) -> io.println(claude.text_content(msg))
/// Error(_) -> io.println("API error")
/// }
/// ```
pub fn message(
client: client.Config,
prompt: String,
) -> Result(Message, ApiError) {
messages.create(messages.RequestParams(
config: client,
model: None,
max_tokens: None,
messages: [message.new_user(prompt)],
tools: tool.registry(),
system: None,
tool_choice: None,
thinking: None,
))
}
/// Extract and concatenate all text content blocks from a message.
///
/// Tool use blocks, thinking blocks, and other non-text blocks are ignored.
/// Multiple text blocks are joined with newlines.
///
/// ## Example
///
/// ```gleam
/// let assert Ok(msg) = claude.message(client, "Hello!")
/// io.println(claude.text_content(msg))
/// ```
pub fn text_content(msg: Message) -> String {
msg.content
|> list.filter_map(fn(block: ContentBlock) {
case block {
Text(text: t, ..) -> Ok(t)
_ -> Error(Nil)
}
})
|> string.join("\n")
}
/// Extract the text content from an agent result's final message.
///
/// This is a convenience function that applies `text_content` to the
/// result's final message.
///
/// ## Example
///
/// ```gleam
/// let assert Ok(result) = claude.run(client, "Hello", tools)
/// io.println(claude.result_text(result))
/// ```
pub fn result_text(result: agent.AgentResult) -> String {
text_content(result.final_message)
}