ClaudeAgentSDK.Client (claude_agent_sdk v0.6.9)

View Source

Bidirectional client for Claude Code with hooks support.

This GenServer maintains a persistent connection to the Claude CLI process, handles control protocol messages, and invokes hook callbacks.

The Client enables:

  • Bidirectional streaming communication
  • Runtime hook callback invocation
  • Control protocol request/response handling
  • Message queueing and delivery

Usage

# Define hook callbacks
def check_bash(input, _tool_use_id, _context) do
  if dangerous?(input), do: Output.deny("Blocked"), else: Output.allow()
end

# Configure options with hooks
options = %Options{
  allowed_tools: ["Bash", "Write"],
  hooks: %{
    pre_tool_use: [
      Matcher.new("Bash", [&check_bash/3])
    ]
  }
}

# Start client
{:ok, pid} = Client.start_link(options)

# Send query
Client.send_message(pid, "Run: echo 'Hello'")

# Receive messages
stream = Client.stream_messages(pid)
Enum.each(stream, &IO.inspect/1)

# Stop client
Client.stop(pid)

With Streaming

{:ok, pid} = Client.start_link(options)

# Start listening in separate process
task = Task.async(fn ->
  Client.stream_messages(pid)
  |> Enum.take_while(&(&1.type != :result))
  |> Enum.to_list()
end)

# Send message
Client.send_message(pid, "Write a function")

# Wait for completion
messages = Task.await(task, :infinity)

See: https://docs.anthropic.com/en/docs/claude-code/sdk

Summary

Types

Client state.

Functions

Returns a specification to start this module under a supervisor.

Gets the currently active agent.

Gets the list of available agent names.

Retrieves the currently active model name.

Returns the server initialization info provided by the CLI.

Sends an interrupt control request to the CLI.

Sends a request in streaming mode, injecting session_id when missing.

Collects messages until a result frame is received.

Rewinds tracked files to their state at a specific user message.

Sends a message to Claude.

Switches to a different agent configuration.

Requests a runtime model switch.

Sets the permission mode at runtime.

Starts the client GenServer.

Stops the client.

Returns a stream of messages from Claude.

Subscribes to the client's message stream and returns a subscription reference.

Types

state()

@type state() :: %{
  port: port() | nil,
  transport: pid() | nil,
  transport_module: module() | nil,
  transport_opts: keyword(),
  options: ClaudeAgentSDK.Options.t(),
  registry: ClaudeAgentSDK.Hooks.Registry.t(),
  hook_callback_timeouts: %{required(String.t()) => pos_integer()},
  subscribers: %{required(reference()) => pid()} | [pid()],
  pending_requests: %{required(String.t()) => {GenServer.from(), reference()}},
  pending_callbacks: %{
    required(String.t()) => %{
      pid: pid(),
      signal: ClaudeAgentSDK.AbortSignal.t(),
      type: :hook | :permission
    }
  },
  initialized: boolean(),
  buffer: String.t(),
  sdk_mcp_servers: %{required(String.t()) => pid()},
  current_model: String.t() | nil,
  pending_model_change: {GenServer.from(), reference()} | nil,
  current_permission_mode: ClaudeAgentSDK.Permission.permission_mode() | nil,
  pending_permission_change: {GenServer.from(), reference()} | nil,
  accumulated_text: String.t(),
  active_subscriber: reference() | nil,
  subscriber_queue: [{reference(), String.t()}],
  server_info: map() | nil,
  init_request_id: String.t() | nil,
  init_timeout_ref: reference() | nil,
  init_timeout_ms: pos_integer() | nil
}

Client state.

Fields:

  • port - Port to Claude CLI process
  • options - Configuration options
  • registry - Hook callback registry
  • hook_callback_timeouts - Map of callback_id => timeout_ms
  • subscribers - Map of ref => pid for streaming subscriptions, or list of pids for legacy
  • pending_requests - Map of request_id => {from, ref}
  • pending_callbacks - Map of request_id => %{pid, signal, type} for in-flight control callbacks
  • initialized - Whether initialization handshake completed
  • buffer - Incomplete JSON buffer
  • sdk_mcp_servers - Map of server_name => registry_pid for SDK MCP servers
  • accumulated_text - Buffer for partial text (streaming, v0.6.0)
  • active_subscriber - Current streaming consumer reference (v0.6.0)
  • subscriber_queue - Pending message queue (v0.6.0)

Functions

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

get_agent(client)

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

Gets the currently active agent.

Parameters

  • client - The client PID

Returns

{:ok, agent_name} or {:error, reason}

Examples

{:ok, :coder} = Client.get_agent(client)

get_available_agents(client)

@spec get_available_agents(pid()) :: {:ok, [atom()]} | {:error, term()}

Gets the list of available agent names.

Parameters

  • client - The client PID

Returns

{:ok, [agent_name]} or {:error, reason}

Examples

{:ok, [:coder, :researcher]} = Client.get_available_agents(client)

get_model(client)

@spec get_model(pid()) :: {:ok, String.t()} | {:error, :model_not_set}

Retrieves the currently active model name.

get_server_info(client)

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

Returns the server initialization info provided by the CLI.

interrupt(client)

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

Sends an interrupt control request to the CLI.

query(client, prompt, session_id \\ "default")

@spec query(pid(), String.t() | Enumerable.t(), String.t()) :: :ok | {:error, term()}

Sends a request in streaming mode, injecting session_id when missing.

Matches Python SDK behavior:

  • String prompts are wrapped as a "user" message with parent_tool_use_id: nil
  • Map prompts (or enumerables of maps) get session_id injected if absent

receive_response(client)

@spec receive_response(pid()) ::
  {:ok, [ClaudeAgentSDK.Message.t()]} | {:error, term()}

Collects messages until a result frame is received.

Useful for workflows that only care about a single response and want to avoid managing streaming state manually.

rewind_files(client, user_message_id)

@spec rewind_files(pid(), String.t()) :: :ok | {:error, term()}

Rewinds tracked files to their state at a specific user message.

Requires Options.enable_file_checkpointing to be enabled when starting the client.

send_message(client, message)

@spec send_message(pid(), String.t() | map()) :: :ok | {:error, term()}

Sends a message to Claude.

In streaming mode, this queues the message for sending.

Parameters

  • client - Client PID
  • message - Message string or map

Returns

:ok or {:error, reason}

Examples

Client.send_message(pid, "Write a hello world function")

set_agent(client, agent_name)

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

Switches to a different agent configuration.

Parameters

  • client - The client PID
  • agent_name - The name of the agent to switch to (atom)

Returns

:ok or {:error, reason}

Examples

Client.set_agent(client, :researcher)

set_model(client, model)

@spec set_model(pid(), String.t()) :: :ok | {:error, term()}

Requests a runtime model switch.

Returns :ok when the CLI confirms the change or {:error, reason} when validation fails or the CLI rejects the request.

set_permission_mode(client, mode)

@spec set_permission_mode(pid(), ClaudeAgentSDK.Permission.permission_mode()) ::
  :ok | {:error, :invalid_permission_mode}

Sets the permission mode at runtime.

Changes how tool permissions are handled for subsequent tool uses.

Parameters

  • client - Client PID
  • mode - Permission mode atom (:default, :accept_edits, :plan, :bypass_permissions)

Returns

  • :ok - Successfully changed mode
  • {:error, :invalid_permission_mode} - Invalid mode provided

Examples

Client.set_permission_mode(pid, :plan)
Client.set_permission_mode(pid, :accept_edits)
Client.set_permission_mode(pid, :bypass_permissions)

start_link(options, opts \\ [])

@spec start_link(
  ClaudeAgentSDK.Options.t(),
  keyword()
) :: GenServer.on_start()

Starts the client GenServer.

Validates hooks configuration, starts Claude CLI process, and performs initialization handshake.

Parameters

  • options - ClaudeAgentSDK.Options struct with hooks configuration

Returns

  • {:ok, pid} - Successfully started
  • {:error, reason} - Failed to start

Examples

options = %Options{
  hooks: %{
    pre_tool_use: [Matcher.new("Bash", [&my_hook/3])]
  }
}

{:ok, pid} = Client.start_link(options)

stop(client)

@spec stop(pid()) :: :ok

Stops the client.

Terminates the CLI process and cleans up resources.

Parameters

  • client - Client PID

Returns

:ok

Examples

Client.stop(pid)

stream_messages(client)

@spec stream_messages(pid()) :: Enumerable.t(ClaudeAgentSDK.Message.t())

Returns a stream of messages from Claude.

Subscribes to the client and yields messages as they arrive.

Parameters

  • client - Client PID

Returns

Enumerable stream of Message structs

Examples

Client.stream_messages(pid)
|> Stream.filter(&(&1.type == :assistant))
|> Enum.to_list()

subscribe(client)

@spec subscribe(pid()) :: {pid(), reference() | nil}

Subscribes to the client's message stream and returns a subscription reference.