ClaudeEx (beam_agent_ex v0.1.0)

Copy Markdown View Source

Elixir wrapper for the Claude Code agent SDK.

Provides idiomatic Elixir access to claude_agent_session (Erlang/OTP gen_statem) with lazy streaming via Stream.resource/3.

Quick Start

# Start a session
{:ok, session} = ClaudeEx.start_session(cli_path: "/usr/local/bin/claude")

# Blocking query — collects all messages
{:ok, messages} = ClaudeEx.query(session, "What is 2 + 2?")

# Streaming query — lazy enumerable
ClaudeEx.stream!(session, "Explain OTP supervision trees")
|> Enum.each(fn msg ->
  case msg.type do
    :assistant -> Enum.each(msg.content_blocks, &IO.inspect/1)
    :result    -> IO.puts(msg.content)
    _          -> :ok
  end
end)

Session Options

  • :cli_path — Path to the Claude CLI executable (default: "claude")
  • :work_dir — Working directory for the CLI subprocess
  • :env — Environment variables as [{key, value}] charlists
  • :buffer_max — Max raw binary buffer in bytes (default: 2MB)
  • :session_id — Resume a previous session (binary)
  • :model — Model to use (binary, e.g. "claude-sonnet-4-6")
  • :fallback_model — Fallback model used by Claude CLI when the primary model is unavailable
  • :system_prompt — System prompt (binary or preset map)
  • :tools — Base built-in tool preset/list passed via --tools
  • :max_turns — Maximum number of turns
  • :resume — Resume a previous session (boolean)
  • :fork_session — Fork from an existing session (boolean)
  • :permission_mode — Permission mode (binary or atom)
  • :permission_prompt_tool_name — Tool name used by Claude for interactive permission prompts
  • :permission_handlerfn(tool_name, tool_input, options) -> result callback
  • :allowed_tools — List of allowed tool names
  • :disallowed_tools — List of disallowed tool names
  • :settings — Settings file path or JSON blob passed via --settings
  • :add_dirs — Extra directories passed via repeated --add-dir
  • :agents — Subagent configurations (map)
  • :mcp_servers — MCP server configurations (map)
  • :output_format — Structured output JSON schema (map)
  • :thinking — Thinking configuration (map)
  • :effort — Effort level (binary)
  • :max_budget_usd — Maximum cost budget (number)
  • :enable_file_checkpointing — Enable file checkpoints (boolean)
  • :plugins — Plugin configurations (list)
  • :hooks — Hook configurations (map)
  • :betas — Beta features to enable (list)
  • :sandbox — Sandbox configuration (map)
  • :debug — Enable debug mode (boolean)
  • :extra_args — Extra CLI arguments (map)
  • :client_app — Client application name (binary)
  • :user_input_handlerfn(request, context) -> {:ok, answer} | {:error, reason} callback for elicitation/user-input requests from the CLI

  • :sdk_mcp_servers — In-process MCP servers (list of server maps from mcp_server/2)
  • :sdk_hooks — SDK lifecycle hooks (list of hook maps from sdk_hook/2,3)

In-Process MCP Servers

Define custom tools as Elixir functions that Claude can call in-process:

tool = ClaudeEx.mcp_tool("greet", "Greet a user",
  %{"type" => "object", "properties" => %{"name" => %{"type" => "string"}}},
  fn input -> {:ok, [%{type: :text, text: "Hello, #{input["name"]}!"}]} end
)
server = ClaudeEx.mcp_server("my-tools", [tool])
{:ok, session} = ClaudeEx.start_session(sdk_mcp_servers: [server])

Session History

Browse past Claude Code sessions without starting a new one:

{:ok, sessions} = ClaudeEx.list_sessions()
{:ok, messages} = ClaudeEx.get_session_messages("session-uuid")

Summary

Functions

Abort the current query. Alias for interrupt/1.

Get account information from the init response.

List active beta features from the system init data.

Get the API key source from the system init data.

Convert a single content_block into a flat message.

Supervisor child specification for embedding a session.

Get the CLI version from the system init data.

Run a command via universal command execution.

Get the current model from session info.

Get the current permission mode from session info.

Delete a session and its messages.

Extract all TodoWrite items from a list of messages.

Filter todo items by status.

Flatten an assistant message (with content_blocks) into individual messages.

Fork a tracked session into a new session ID.

Read all messages from a past Claude Code session transcript on disk.

Get session metadata by ID.

Get messages for a session.

Get messages with options.

Get the current health/state of a session.

Interrupt the current active query.

List available agents from the system init data.

List configured MCP servers from the system init data.

List past Claude Code session transcripts from disk.

List available plugins from the system init data.

List all tracked sessions.

List sessions with filters.

List available skills from the system init data.

List available tools from the system init data.

Create an MCP server with a list of tools.

Query MCP server health and status.

Create an MCP tool definition for in-process tool handling.

Convert a single flat message into a content_block.

Convert a list of flat messages into content_block() format.

Normalize a list of messages from any adapter into a uniform flat stream.

Get the output style from the system init data.

Send a query and collect all response messages (blocking).

Reconnect a failed MCP server by name.

Revert the visible session history to a prior boundary.

Revert file changes to a checkpoint identified by UUID.

Create an SDK lifecycle hook.

Create an SDK lifecycle hook with a matcher filter.

Send a raw control message to the session.

Check server health. Maps to session health + adapter info for Claude.

Query session capabilities and initialization data.

Set the maximum thinking tokens at runtime.

Dynamically add or replace MCP server configurations.

Change the model at runtime during a session.

Change the permission mode at runtime.

Create or replace share state for the current session.

Start a new Claude Code session.

Gracefully stop a session, closing the CLI subprocess.

Stop a running agent task by task ID.

Return a Stream that does not raise on errors.

Return a lazy Stream of messages for the given prompt.

Submit feedback via universal feedback tracking.

Generate and store a summary for the current session.

List available agents from the init response.

List available slash commands from the init response.

List available models from the init response.

Fork an existing thread.

List all threads for this session.

Read thread metadata, optionally including visible messages.

Resume an existing thread.

Rollback the visible thread history.

Start a new conversation thread.

Get a summary of todo counts by status.

Enable or disable an MCP server at runtime.

Respond to an agent request via universal turn response.

Clear any stored session revert state.

Revoke share state for the current session.

Get the working directory from the system init data.

Types

content_block()

@type content_block() :: %{
  :type => :text | :thinking | :tool_use | :tool_result | :raw,
  optional(:text) => binary(),
  optional(:thinking) => binary(),
  optional(:id) => binary(),
  optional(:name) => binary(),
  optional(:input) => map(),
  optional(:tool_use_id) => binary(),
  optional(:content) => binary(),
  optional(:raw) => map()
}

hook_callback()

@type hook_callback() :: (hook_context() -> :ok | {:deny, binary()})

hook_context()

@type hook_context() :: %{
  event: atom(),
  agent_id: binary(),
  agent_transcript_path: binary(),
  agent_type: binary(),
  content: binary(),
  duration_ms: non_neg_integer(),
  interrupt: boolean(),
  params: map(),
  permission_prompt_tool_name: binary(),
  permission_suggestions: [any()],
  prompt: binary(),
  reason: term(),
  session_id: binary(),
  stop_hook_active: boolean(),
  stop_reason: atom() | binary(),
  system_info: map(),
  tool_input: map(),
  tool_name: binary(),
  tool_use_id: binary(),
  updated_permissions: map()
}

hook_map()

@type hook_map() :: %{callback: hook_callback(), event: atom()}

hook_with_matcher_map()

@type hook_with_matcher_map() :: %{
  callback: hook_callback(),
  event: atom(),
  matcher: %{tool_name: binary()},
  compiled_re: {:re_pattern, term(), term(), term(), term()}
}

mcp_server_map()

@type mcp_server_map() :: %{
  name: binary(),
  tools: [mcp_tool_map()],
  version: binary()
}

mcp_tool_map()

@type mcp_tool_map() :: %{
  description: binary(),
  handler: (map() -> {:error, binary()} | {:ok, [any()]}),
  input_schema: map(),
  name: binary()
}

message()

@type message() :: %{
  :type =>
    :text
    | :assistant
    | :tool_use
    | :tool_result
    | :system
    | :result
    | :error
    | :user
    | :control_request
    | :control_response
    | :stream_event
    | :rate_limit_event
    | :tool_progress
    | :tool_use_summary
    | :thinking
    | :auth_status
    | :prompt_suggestion
    | :raw,
  optional(:content) => binary(),
  optional(:tool_name) => binary(),
  optional(:tool_input) => map(),
  optional(:raw) => map(),
  optional(:timestamp) => integer(),
  optional(:uuid) => binary(),
  optional(:session_id) => binary(),
  optional(:content_blocks) => [content_block()],
  optional(:parent_tool_use_id) => binary() | nil,
  optional(:message_id) => binary(),
  optional(:model) => binary(),
  optional(:error_info) => map(),
  optional(:system_info) => map(),
  optional(:duration_ms) => non_neg_integer(),
  optional(:duration_api_ms) => non_neg_integer(),
  optional(:num_turns) => non_neg_integer(),
  optional(:stop_reason) => binary(),
  optional(:stop_reason_atom) => stop_reason(),
  optional(:usage) => map(),
  optional(:model_usage) => map(),
  optional(:total_cost_usd) => number(),
  optional(:is_error) => boolean(),
  optional(:subtype) => binary(),
  optional(:errors) => [binary()],
  optional(:structured_output) => term(),
  optional(:permission_denials) => list(),
  optional(:fast_mode_state) => map(),
  optional(:is_replay) => boolean(),
  optional(:request_id) => binary(),
  optional(:request) => map(),
  optional(:response) => map(),
  optional(:rate_limit_status) => binary(),
  optional(:resets_at) => number(),
  optional(:rate_limit_type) => binary(),
  optional(:utilization) => number(),
  optional(:overage_status) => binary(),
  optional(:overage_resets_at) => number(),
  optional(:overage_disabled_reason) => binary(),
  optional(:is_using_overage) => boolean(),
  optional(:surpassed_threshold) => number()
}

permission_mode()

@type permission_mode() ::
  :default | :accept_edits | :bypass_permissions | :plan | :dont_ask

query_opt()

@type query_opt() ::
  {:system_prompt, binary()}
  | {:tools, [binary()] | map()}
  | {:allowed_tools, [binary()]}
  | {:disallowed_tools, [binary()]}
  | {:max_tokens, pos_integer()}
  | {:max_turns, pos_integer()}
  | {:permission_mode, binary()}
  | {:model, binary()}
  | {:timeout, timeout()}
  | {:output_format, map()}
  | {:thinking, map()}
  | {:effort, binary()}
  | {:max_budget_usd, number()}
  | {:agent, binary()}

session()

@type session() :: pid()

session_info_map()

@type session_info_map() :: %{
  session_id: binary(),
  adapter: atom(),
  created_at: integer(),
  cwd: binary(),
  extra: map(),
  message_count: non_neg_integer(),
  model: binary(),
  updated_at: integer()
}

session_opt()

@type session_opt() ::
  {:cli_path, Path.t()}
  | {:work_dir, Path.t()}
  | {:env, [{charlist(), charlist()}]}
  | {:buffer_max, pos_integer()}
  | {:session_id, binary()}
  | {:model, binary()}
  | {:fallback_model, binary()}
  | {:system_prompt, binary() | map()}
  | {:tools, [binary()] | map()}
  | {:max_turns, pos_integer()}
  | {:resume, boolean()}
  | {:fork_session, boolean()}
  | {:continue, boolean()}
  | {:persist_session, boolean()}
  | {:permission_mode, binary() | permission_mode()}
  | {:permission_prompt_tool_name, binary()}
  | {:permission_handler, (binary(), map(), map() -> term())}
  | {:allowed_tools, [binary()]}
  | {:disallowed_tools, [binary()]}
  | {:settings, binary()}
  | {:add_dirs, [binary() | String.t() | Path.t()]}
  | {:agents, map()}
  | {:mcp_servers, map()}
  | {:output_format, map()}
  | {:thinking, map()}
  | {:effort, binary()}
  | {:max_budget_usd, number()}
  | {:enable_file_checkpointing, boolean()}
  | {:setting_sources, [binary()]}
  | {:plugins, [map()]}
  | {:hooks, map()}
  | {:betas, [binary()]}
  | {:include_partial_messages, boolean()}
  | {:prompt_suggestions, boolean()}
  | {:sandbox, map()}
  | {:debug, boolean()}
  | {:debug_file, binary()}
  | {:extra_args, map()}
  | {:client_app, binary()}
  | {:sdk_mcp_servers, [map()]}
  | {:sdk_hooks, [map()]}

share_info_map()

@type share_info_map() :: %{
  created_at: integer(),
  session_id: binary(),
  share_id: binary(),
  status: :active
}

stop_reason()

@type stop_reason() ::
  :end_turn
  | :max_tokens
  | :stop_sequence
  | :refusal
  | :tool_use_stop
  | :unknown_stop

summary_info_map()

@type summary_info_map() :: %{
  content: binary(),
  generated_at: integer(),
  generated_by: binary(),
  message_count: non_neg_integer(),
  session_id: binary()
}

thread_info_map()

@type thread_info_map() :: %{
  created_at: integer(),
  message_count: non_neg_integer(),
  session_id: binary(),
  status: :active | :archived | :completed | :paused,
  thread_id: binary(),
  updated_at: integer(),
  visible_message_count: non_neg_integer(),
  archived: boolean(),
  archived_at: integer(),
  metadata: map(),
  name: binary(),
  parent_thread_id: binary(),
  summary: map()
}

thread_read_info_map()

@type thread_read_info_map() :: %{thread: thread_info_map(), messages: [map()]}

Functions

abort(session)

@spec abort(pid()) :: :ok | {:error, term()}

Abort the current query. Alias for interrupt/1.

account_info(session)

@spec account_info(session()) :: {:ok, map()} | {:error, term()}

Get account information from the init response.

Returns account details (email, org, subscription type, etc.) from the initialize control_response.

Examples

{:ok, account} = ClaudeEx.account_info(session)
account["email"]  # => "user@example.com"

active_betas(session)

@spec active_betas(pid()) :: {:ok, list()} | {:error, term()}

List active beta features from the system init data.

api_key_source(session)

@spec api_key_source(pid()) :: {:ok, binary() | nil} | {:error, term()}

Get the API key source from the system init data.

block_to_message(block)

@spec block_to_message(content_block()) :: %{
  type: :raw | :text | :thinking | :tool_result | :tool_use,
  content: term(),
  raw: term(),
  tool_input: term(),
  tool_name: term(),
  tool_use_id: term()
}

Convert a single content_block into a flat message.

child_spec(opts)

@spec child_spec([session_opt()]) :: Supervisor.child_spec()

Supervisor child specification for embedding a session.

Examples

children = [
  {ClaudeEx, cli_path: "claude", work_dir: "/my/project"}
]
Supervisor.start_link(children, strategy: :one_for_one)

cli_version(session)

@spec cli_version(pid()) :: {:ok, binary() | nil} | {:error, term()}

Get the CLI version from the system init data.

command_run(session, command, opts \\ %{})

@spec command_run(pid(), binary(), map()) ::
  {:ok, %{exit_code: integer(), output: binary()}}
  | {:error,
     {:port_exit, term()} | {:port_failed, term()} | {:timeout, timeout()}}

Run a command via universal command execution.

current_model(session)

@spec current_model(pid()) :: {:ok, binary() | nil} | {:error, term()}

Get the current model from session info.

Extracts from the session's model field or system init data.

current_permission_mode(session)

@spec current_permission_mode(pid()) ::
  {:ok, atom() | binary() | nil} | {:error, term()}

Get the current permission mode from session info.

delete_session(session_id)

@spec delete_session(binary()) :: :ok

Delete a session and its messages.

extract_todos(messages)

@spec extract_todos([message()]) :: [BeamAgent.Todo.todo_item()]

Extract all TodoWrite items from a list of messages.

Scans assistant messages for TodoWrite tool use blocks and returns a flat list of todo items with :content, :status, and optional :active_form fields.

filter_todos(todos, status)

Filter todo items by status.

Valid statuses: :pending, :in_progress, :completed.

flatten_assistant(message)

@spec flatten_assistant(map()) :: [map()]

Flatten an assistant message (with content_blocks) into individual messages.

Non-assistant messages pass through as a single-element list. Context fields from the parent are propagated to children.

fork_session(session, opts)

@spec fork_session(pid(), map()) :: {:ok, session_info_map()} | {:error, :not_found}

Fork a tracked session into a new session ID.

get_native_session_messages(session_id, opts \\ [])

@spec get_native_session_messages(binary(), [{:config_dir, binary()}]) ::
  {:ok, [map()]} | {:error, term()}

Read all messages from a past Claude Code session transcript on disk.

Parses the full JSONL file and returns messages in conversation order.

Options

  • :config_dir — Override the Claude config directory (default: ~/.claude)

Examples

{:ok, messages} = ClaudeEx.get_native_session_messages("session-uuid-123")

get_session(session_id)

@spec get_session(binary()) :: {:ok, session_info_map()} | {:error, :not_found}

Get session metadata by ID.

get_session_messages(session_id)

@spec get_session_messages(binary()) :: {:ok, [message()]} | {:error, :not_found}

Get messages for a session.

get_session_messages(session_id, opts)

@spec get_session_messages(binary(), map()) ::
  {:ok, [message()]} | {:error, :not_found}

Get messages with options.

health(session)

@spec health(session()) ::
  :ready | :connecting | :initializing | :active_query | :error

Get the current health/state of a session.

Examples

:ready = ClaudeEx.health(session)

interrupt(session)

@spec interrupt(pid()) :: :ok | {:error, term()}

Interrupt the current active query.

Sends an interrupt signal to the CLI subprocess. The current query will terminate and the session returns to idle state.

list_agents(session)

@spec list_agents(pid()) :: {:ok, list()} | {:error, term()}

List available agents from the system init data.

list_mcp_servers(session)

@spec list_mcp_servers(pid()) :: {:ok, list()} | {:error, term()}

List configured MCP servers from the system init data.

list_native_sessions(opts \\ [])

@spec list_native_sessions(config_dir: binary(), cwd: binary(), limit: pos_integer()) ::
  {:ok,
   [
     %{
       file_path: binary(),
       modified_at: integer(),
       session_id: binary(),
       cwd: binary(),
       model: binary()
     }
   ]}

List past Claude Code session transcripts from disk.

Scans ~/.claude/projects/ for JSONL transcript files and returns metadata (session_id, model, timestamps) sorted by most recent first.

Options

  • :config_dir — Override the Claude config directory (default: ~/.claude)
  • :cwd — Filter to sessions for a specific working directory
  • :limit — Maximum number of sessions to return

Examples

{:ok, sessions} = ClaudeEx.list_native_sessions()
{:ok, sessions} = ClaudeEx.list_native_sessions(limit: 10)

list_plugins(session)

@spec list_plugins(pid()) :: {:ok, list()} | {:error, term()}

List available plugins from the system init data.

list_sessions()

@spec list_sessions() :: {:ok, [session_info_map()]}

List all tracked sessions.

list_sessions(opts)

@spec list_sessions(map()) :: {:ok, [session_info_map()]}

List sessions with filters.

list_skills(session)

@spec list_skills(pid()) :: {:ok, list()} | {:error, term()}

List available skills from the system init data.

list_tools(session)

@spec list_tools(pid()) :: {:ok, list()} | {:error, term()}

List available tools from the system init data.

mcp_server(name, tools)

@spec mcp_server(binary(), [mcp_tool_map()]) :: mcp_server_map()

Create an MCP server with a list of tools.

The server is registered with the session at startup and handles JSON-RPC tool calls from Claude in-process.

Examples

tool = ClaudeEx.mcp_tool("greet", "Greet user", %{"type" => "object"},
  fn _input -> {:ok, [%{type: :text, text: "Hello!"}]} end
)
server = ClaudeEx.mcp_server("my-tools", [tool])
{:ok, session} = ClaudeEx.start_session(sdk_mcp_servers: [server])

mcp_server_status(session)

@spec mcp_server_status(session()) :: {:ok, term()} | {:error, term()}

Query MCP server health and status.

Returns connection status, availability, and diagnostics for all configured MCP servers in the session.

Examples

{:ok, status} = ClaudeEx.mcp_server_status(session)

mcp_tool(name, description, input_schema, handler)

@spec mcp_tool(binary(), binary(), map(), (map() ->
                                       {:error, binary()} | {:ok, [map()]})) ::
  mcp_tool_map()

Create an MCP tool definition for in-process tool handling.

The handler function receives the tool input as a map and must return {:ok, [content_result()]} or {:error, binary()}.

Examples

tool = ClaudeEx.mcp_tool("echo", "Echo input",
  %{"type" => "object", "properties" => %{"text" => %{"type" => "string"}}},
  fn input -> {:ok, [%{type: :text, text: input["text"]}]} end
)

message_to_block(message)

@spec message_to_block(map()) :: content_block()

Convert a single flat message into a content_block.

messages_to_blocks(messages)

@spec messages_to_blocks([map()]) :: [content_block()]

Convert a list of flat messages into content_block() format.

Supported types (text, thinking, tool_use, tool_result) map to their block equivalents. Other types are wrapped in raw blocks.

normalize_messages(messages)

@spec normalize_messages([map()]) :: [map()]

Normalize a list of messages from any adapter into a uniform flat stream.

Claude produces assistant messages with nested content_blocks. All other adapters produce individual typed messages (text, tool_use, etc.). This function flattens both into a uniform stream where each message has a single, specific type — never nested content_blocks.

Context fields (uuid, session_id, model, timestamp) from assistant messages are propagated to flattened children.

Examples

# Works identically regardless of which adapter produced messages:
ClaudeEx.normalize_messages(messages)
|> Enum.filter(& &1.type == :text)
|> Enum.map(& &1.content)
|> Enum.join("")

output_style(session)

@spec output_style(pid()) :: {:ok, binary() | nil} | {:error, term()}

Get the output style from the system init data.

query(session, prompt, opts \\ [])

@spec query(session(), binary(), [query_opt()]) ::
  {:ok, [message()]} | {:error, term()}

Send a query and collect all response messages (blocking).

Returns the complete list of messages once the query finishes. This is the simple, synchronous interface.

Examples

{:ok, messages} = ClaudeEx.query(session, "Hello!")
last = List.last(messages)
IO.puts(last.content)

reconnect_mcp_server(session, server_name)

@spec reconnect_mcp_server(session(), binary()) :: {:ok, term()} | {:error, term()}

Reconnect a failed MCP server by name.

Examples

{:ok, _} = ClaudeEx.reconnect_mcp_server(session, "my_server")

revert_session(session, selector)

@spec revert_session(pid(), map()) ::
  {:ok, session_info_map()} | {:error, :invalid_selector | :not_found}

Revert the visible session history to a prior boundary.

rewind_files(session, checkpoint_uuid)

@spec rewind_files(session(), binary()) :: {:ok, term()} | {:error, term()}

Revert file changes to a checkpoint identified by UUID.

Only meaningful when file checkpointing is enabled in session opts.

Examples

{:ok, _} = ClaudeEx.rewind_files(session, "msg-uuid-123")

sdk_hook(event, callback)

@spec sdk_hook(atom(), hook_callback()) :: hook_map()

Create an SDK lifecycle hook.

Hooks fire at key session lifecycle points. Events:

  • :pre_tool_use — before tool execution (can deny)
  • :post_tool_use — after tool result received
  • :stop — when result/completion received
  • :session_start — when session enters ready state
  • :session_end — when session terminates
  • :user_prompt_submit — before query sent (can deny)

Examples

hook = ClaudeEx.sdk_hook(:pre_tool_use, fn ctx ->
  case ctx.tool_name do
    "Bash" -> {:deny, "No shell access"}
    _ -> :ok
  end
end)
{:ok, session} = ClaudeEx.start_session(sdk_hooks: [hook])

sdk_hook(event, callback, matcher)

@spec sdk_hook(atom(), hook_callback(), %{tool_name: binary()}) ::
  hook_with_matcher_map()

Create an SDK lifecycle hook with a matcher filter.

The matcher's :tool_name (exact string or regex) restricts which tools trigger the hook. Only relevant for tool-related events.

Examples

# Only fire on Bash tool
hook = ClaudeEx.sdk_hook(:pre_tool_use,
  fn _ctx -> {:deny, "blocked"} end,
  %{tool_name: "Bash"})

# Fire on Read* tools (regex)
hook = ClaudeEx.sdk_hook(:pre_tool_use,
  fn _ctx -> :ok end,
  %{tool_name: "Read.*"})

send_control(session, method, params \\ %{})

@spec send_control(pid(), binary(), map()) :: {:ok, term()} | {:error, term()}

Send a raw control message to the session.

Low-level interface for sending arbitrary control protocol messages. Most users should prefer the higher-level convenience functions.

Examples

ClaudeEx.send_control(session, "setModel", %{"model" => "claude-sonnet-4-6"})

server_health(session)

@spec server_health(pid()) ::
  {:ok,
   %{
     adapter: :claude,
     health: :active_query | :connecting | :error | :initializing | :ready
   }}

Check server health. Maps to session health + adapter info for Claude.

session_info(session)

@spec session_info(session()) :: {:ok, map()} | {:error, term()}

Query session capabilities and initialization data.

Returns a map with:

  • :session_id — the session ID
  • :system_info — parsed system init metadata (tools, model, etc.)
  • :init_response — raw initialize control_response

Available in all session states (connecting, initializing, ready, active_query, error).

Examples

{:ok, info} = ClaudeEx.session_info(session)
info.system_info.model  # => "claude-sonnet-4-20250514"
info.system_info.tools  # => ["Read", "Write", "Bash"]

set_max_thinking_tokens(session, max_tokens)

@spec set_max_thinking_tokens(session(), pos_integer()) ::
  {:ok, term()} | {:error, term()}

Set the maximum thinking tokens at runtime.

Controls how many tokens the model can use for its internal reasoning/thinking process.

Examples

{:ok, _} = ClaudeEx.set_max_thinking_tokens(session, 8192)

set_mcp_servers(session, servers)

@spec set_mcp_servers(session(), map()) :: {:ok, term()} | {:error, term()}

Dynamically add or replace MCP server configurations.

Accepts a map of server name => server config. Existing servers with the same name are replaced; others are unaffected.

Examples

servers = %{"my_server" => %{"command" => "node", "args" => ["server.js"]}}
{:ok, _} = ClaudeEx.set_mcp_servers(session, servers)

set_model(session, model)

@spec set_model(session(), binary()) :: {:ok, binary()} | {:error, term()}

Change the model at runtime during a session.

Examples

:ok = ClaudeEx.set_model(session, "claude-sonnet-4-20250514")

set_permission_mode(session, mode)

@spec set_permission_mode(session(), binary()) :: {:ok, binary()} | {:error, term()}

Change the permission mode at runtime.

Examples

:ok = ClaudeEx.set_permission_mode(session, "acceptEdits")

share_session(session)

@spec share_session(pid()) :: {:ok, share_info_map()} | {:error, :not_found}

Create or replace share state for the current session.

share_session(session, opts)

@spec share_session(pid(), map()) :: {:ok, share_info_map()} | {:error, :not_found}

start_session(opts \\ [])

@spec start_session([session_opt()]) :: {:ok, session()} | {:error, term()}

Start a new Claude Code session.

Returns {:ok, pid} on success. The session process can be added to your supervision tree via ClaudeEx.child_spec/1.

Examples

{:ok, session} = ClaudeEx.start_session(cli_path: "claude")
{:ok, session} = ClaudeEx.start_session(work_dir: "/my/project")
{:ok, session} = ClaudeEx.start_session(
  cli_path: "claude",
  model: "claude-sonnet-4-20250514",
  permission_mode: :accept_edits
)

stop(session)

@spec stop(session()) :: :ok

Gracefully stop a session, closing the CLI subprocess.

stop_task(session, task_id)

@spec stop_task(session(), binary()) :: {:ok, term()} | {:error, term()}

Stop a running agent task by task ID.

Examples

{:ok, _} = ClaudeEx.stop_task(session, "task-abc")

stream(session, prompt, opts \\ [])

@spec stream(session(), binary(), [query_opt()]) :: Enumerable.t()

Return a Stream that does not raise on errors.

Like stream!/3 but wraps messages in {:ok, msg} tuples and returns {:error, reason} on failure instead of raising.

stream!(session, prompt, opts \\ [])

@spec stream!(session(), binary(), [query_opt()]) :: Enumerable.t()

Return a lazy Stream of messages for the given prompt.

Uses Stream.resource/3 to implement demand-driven consumption: the gen_statem only parses the next JSONL line when the stream consumer requests it. The query is dispatched to the CLI immediately when stream!/3 is called; message consumption is lazy/pull-based.

The stream halts automatically when a :result or :error message is received, or when the query completes.

Examples

ClaudeEx.stream!(session, "Explain GenServer")
|> Stream.filter(& &1.type == :assistant)
|> Enum.flat_map(& &1.content_blocks)
|> Enum.filter(& &1.type == :text)
|> Enum.map(& &1.text)
|> Enum.join("")

# With options
ClaudeEx.stream!(session, "Hello", system_prompt: "Be brief")
|> Enum.to_list()

submit_feedback(session, feedback)

@spec submit_feedback(pid(), map()) :: :ok

Submit feedback via universal feedback tracking.

summarize_session(session)

@spec summarize_session(pid()) :: {:ok, summary_info_map()} | {:error, :not_found}

Generate and store a summary for the current session.

summarize_session(session, opts)

@spec summarize_session(pid(), map()) ::
  {:ok, summary_info_map()} | {:error, :not_found}

supported_agents(session)

@spec supported_agents(session()) :: {:ok, list()} | {:error, term()}

List available agents from the init response.

Examples

{:ok, agents} = ClaudeEx.supported_agents(session)

supported_commands(session)

@spec supported_commands(session()) :: {:ok, list()} | {:error, term()}

List available slash commands from the init response.

Returns the commands array from the initialize control_response, or an empty list if not yet initialized.

Examples

{:ok, commands} = ClaudeEx.supported_commands(session)
Enum.each(commands, &IO.inspect/1)

supported_models(session)

@spec supported_models(session()) :: {:ok, list()} | {:error, term()}

List available models from the init response.

Examples

{:ok, models} = ClaudeEx.supported_models(session)

thread_archive(session, thread_id)

@spec thread_archive(pid(), binary()) ::
  {:ok, thread_info_map()} | {:error, :not_found}

Archive a thread.

thread_fork(session, thread_id)

@spec thread_fork(pid(), binary()) :: {:ok, thread_info_map()} | {:error, :not_found}

Fork an existing thread.

thread_fork(session, thread_id, opts)

@spec thread_fork(pid(), binary(), map()) ::
  {:ok, thread_info_map()} | {:error, :not_found}

thread_list(session)

@spec thread_list(pid()) :: {:ok, [thread_info_map()]}

List all threads for this session.

thread_read(session, thread_id)

@spec thread_read(pid(), binary()) ::
  {:ok, thread_read_info_map()} | {:error, :not_found}

Read thread metadata, optionally including visible messages.

thread_read(session, thread_id, opts)

@spec thread_read(pid(), binary(), map()) ::
  {:ok, thread_read_info_map()} | {:error, :not_found}

thread_resume(session, thread_id)

@spec thread_resume(pid(), binary()) ::
  {:ok, thread_info_map()} | {:error, :not_found}

Resume an existing thread.

thread_rollback(session, thread_id, selector)

@spec thread_rollback(pid(), binary(), map()) ::
  {:ok, thread_info_map()} | {:error, :invalid_selector | :not_found}

Rollback the visible thread history.

thread_start(session, opts \\ %{})

@spec thread_start(pid(), map()) ::
  {:ok,
   %{
     archived: false,
     created_at: integer(),
     message_count: 0,
     metadata: map(),
     name: binary(),
     session_id: binary(),
     status: :active,
     thread_id: binary(),
     updated_at: integer(),
     visible_message_count: 0,
     parent_thread_id: binary()
   }}

Start a new conversation thread.

thread_unarchive(session, thread_id)

@spec thread_unarchive(pid(), binary()) ::
  {:ok, thread_info_map()} | {:error, :not_found}

Unarchive a thread.

todo_summary(todos)

@spec todo_summary([BeamAgent.Todo.todo_item()]) :: %{
  :total => non_neg_integer(),
  required(atom()) => non_neg_integer()
}

Get a summary of todo counts by status.

Returns a map like %{pending: 2, in_progress: 1, completed: 3, total: 6}.

toggle_mcp_server(session, server_name, enabled)

@spec toggle_mcp_server(session(), binary(), boolean()) ::
  {:ok, term()} | {:error, term()}

Enable or disable an MCP server at runtime.

Examples

{:ok, _} = ClaudeEx.toggle_mcp_server(session, "my_server", false)
{:ok, _} = ClaudeEx.toggle_mcp_server(session, "my_server", true)

turn_respond(session, request_id, params)

@spec turn_respond(pid(), binary(), map()) ::
  :ok | {:error, :not_found | :already_resolved}

Respond to an agent request via universal turn response.

unrevert_session(session)

@spec unrevert_session(pid()) :: {:ok, session_info_map()} | {:error, :not_found}

Clear any stored session revert state.

unshare_session(session)

@spec unshare_session(pid()) :: :ok | {:error, :not_found}

Revoke share state for the current session.

working_directory(session)

@spec working_directory(pid()) :: {:ok, binary() | nil} | {:error, term()}

Get the working directory from the system init data.