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) }