Class: ClaudeAgentSDK::Client

Inherits:
Object
  • Object
show all
Defined in:
lib/claude_agent_sdk.rb

Overview

Client for bidirectional, interactive conversations with Claude Code

This client provides full control over the conversation flow with support for streaming, hooks, permission callbacks, and dynamic message sending. The Client class always uses streaming mode for bidirectional communication.

Examples:

Basic usage

Async do
  client = ClaudeAgentSDK::Client.new
  client.connect  # No arguments needed - automatically uses streaming mode

  client.query("What is the capital of France?")
  client.receive_response do |msg|
    puts msg if msg.is_a?(ClaudeAgentSDK::AssistantMessage)
  end

  client.disconnect
end

With hooks

options = ClaudeAgentOptions.new(
  hooks: {
    'PreToolUse' => [
      HookMatcher.new(
        matcher: 'Bash',
        hooks: [
          ->(input, tool_use_id, context) {
            # Return hook output
            {}
          }
        ]
      )
    ]
  }
)
client = ClaudeAgentSDK::Client.new(options: options)

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options: nil, transport_class: SubprocessCLITransport, transport_args: {}) ⇒ Client

Returns a new instance of Client.

Parameters:

  • options (ClaudeAgentOptions, nil) (defaults to: nil)

    Configuration options

  • transport_class (Class) (defaults to: SubprocessCLITransport)

    Transport class to use (must implement Transport interface). Defaults to SubprocessCLITransport.

  • transport_args (Hash) (defaults to: {})

    Additional keyword arguments passed to transport_class.new(options, **transport_args)



298
299
300
301
302
303
304
305
# File 'lib/claude_agent_sdk.rb', line 298

def initialize(options: nil, transport_class: SubprocessCLITransport, transport_args: {})
  @options = options || ClaudeAgentOptions.new
  @transport_class = transport_class
  @transport_args = transport_args
  @transport = nil
  @query_handler = nil
  @connected = false
end

Instance Attribute Details

#query_handlerObject (readonly)

Returns the value of attribute query_handler.



292
293
294
# File 'lib/claude_agent_sdk.rb', line 292

def query_handler
  @query_handler
end

Instance Method Details

#connect(prompt = nil) ⇒ Object

Connect to Claude with optional initial prompt.

Client always uses streaming mode for bidirectional communication. If you pass a String, it will be sent as an initial user message after the connection is established. If you pass an Enumerator, it should yield JSONL messages (e.g., from ClaudeAgentSDK::Streaming.user_message).

Parameters:

  • prompt (String, Enumerator, nil) (defaults to: nil)

    Initial prompt or message stream

Raises:

  • (ArgumentError)


315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
# File 'lib/claude_agent_sdk.rb', line 315

def connect(prompt = nil)
  return if @connected

  raise ArgumentError, "prompt must be a String, an Enumerator, or nil (got #{prompt.class})" unless prompt.nil? || prompt.is_a?(String) || prompt.respond_to?(:each)

  # Validate and configure permission settings
  configured_options = @options
  if @options.can_use_tool
    # can_use_tool and permission_prompt_tool_name are mutually exclusive
    raise ArgumentError, 'can_use_tool callback cannot be used with permission_prompt_tool_name' if @options.permission_prompt_tool_name

    # Set permission_prompt_tool_name to stdio for control protocol
    configured_options = @options.dup_with(permission_prompt_tool_name: 'stdio')
  end

  # Client always uses streaming mode; keep stdin open for bidirectional communication.
  @transport = @transport_class.new(configured_options, **@transport_args)
  @transport.connect

  # Extract SDK MCP servers
  sdk_mcp_servers = {}
  if configured_options.mcp_servers.is_a?(Hash)
    configured_options.mcp_servers.each do |name, config|
      sdk_mcp_servers[name] = config[:instance] if config.is_a?(Hash) && config[:type] == 'sdk'
    end
  end

  # Convert hooks to internal format
  hooks = convert_hooks_to_internal_format(configured_options.hooks) if configured_options.hooks

  # Extract exclude_dynamic_sections from preset system prompt for the
  # initialize request (older CLIs ignore unknown initialize fields)
  exclude_dynamic_sections = extract_exclude_dynamic_sections(configured_options.system_prompt)

  # Create Query handler
  @query_handler = Query.new(
    transport: @transport,
    is_streaming_mode: true,
    can_use_tool: configured_options.can_use_tool,
    hooks: hooks,
    sdk_mcp_servers: sdk_mcp_servers,
    agents: configured_options.agents,
    exclude_dynamic_sections: exclude_dynamic_sections
  )

  # Start query handler and initialize
  @query_handler.start
  @query_handler.initialize_protocol

  # Resolve callable observers into fresh instances (thread-safe for global defaults)
  @resolved_observers = ClaudeAgentSDK.resolve_observers(@options.observers)

  @connected = true

  # Optionally send initial prompt/messages after connection is ready.
  case prompt
  when nil
    nil
  when String
    query(prompt)
  else
    prompt.each do |message_json|
      writeln(message_json.to_s)
    end
  end
end

#disconnectObject

Disconnect from Claude



515
516
517
518
519
520
521
522
523
# File 'lib/claude_agent_sdk.rb', line 515

def disconnect
  return unless @connected

  ClaudeAgentSDK.notify_observers(@resolved_observers || [], :on_close)
  @query_handler&.close
  @query_handler = nil
  @transport = nil
  @connected = false
end

#get_context_usageHash

Get a breakdown of current context window usage by category. Returns token counts per category (system prompt, tools, messages, etc.), total/max tokens, model info, MCP tools, memory files, and more.

Returns:

  • (Hash)

    Context usage response

Raises:



495
496
497
498
# File 'lib/claude_agent_sdk.rb', line 495

def get_context_usage
  raise CLIConnectionError, 'Not connected. Call connect() first' unless @connected
  @query_handler.get_context_usage
end

#get_mcp_statusHash

Get current MCP server connection status (only works with streaming mode)

Returns:

  • (Hash)

    MCP status information, including mcpServers list

Raises:



502
503
504
505
# File 'lib/claude_agent_sdk.rb', line 502

def get_mcp_status
  raise CLIConnectionError, 'Not connected. Call connect() first' unless @connected
  @query_handler.get_mcp_status
end

#get_server_infoHash

Get server initialization info including available commands and output styles

Returns:

  • (Hash)

    Server info

Raises:



509
510
511
512
# File 'lib/claude_agent_sdk.rb', line 509

def get_server_info
  raise CLIConnectionError, 'Not connected. Call connect() first' unless @connected
  server_info
end

#interruptObject

Send interrupt signal

Raises:



435
436
437
438
# File 'lib/claude_agent_sdk.rb', line 435

def interrupt
  raise CLIConnectionError, 'Not connected. Call connect() first' unless @connected
  @query_handler.interrupt
end

#query(prompt, session_id: 'default') ⇒ Object

Send a query to Claude

Parameters:

  • prompt (String)

    The prompt to send

  • session_id (String) (defaults to: 'default')

    Session identifier

Raises:



385
386
387
388
389
390
391
392
393
394
395
396
# File 'lib/claude_agent_sdk.rb', line 385

def query(prompt, session_id: 'default')
  raise CLIConnectionError, 'Not connected. Call connect() first' unless @connected

  ClaudeAgentSDK.notify_observers(@resolved_observers, :on_user_prompt, prompt)
  message = {
    type: 'user',
    message: { role: 'user', content: prompt },
    parent_tool_use_id: nil,
    session_id: session_id
  }
  writeln(JSON.generate(message))
end

#receive_messages {|Message| ... } ⇒ Object

Receive all messages from Claude

Yields:

  • (Message)

    Each message received

Raises:



400
401
402
403
404
405
406
407
408
409
410
411
412
# File 'lib/claude_agent_sdk.rb', line 400

def receive_messages(&block)
  return enum_for(:receive_messages) unless block

  raise CLIConnectionError, 'Not connected. Call connect() first' unless @connected

  @query_handler.receive_messages do |data|
    message = MessageParser.parse(data)
    if message
      ClaudeAgentSDK.notify_observers(@resolved_observers, :on_message, message)
      FiberBoundary.invoke { block.call(message) }
    end
  end
end

#receive_response {|Message| ... } ⇒ Object

Receive messages until a ResultMessage is received

Yields:

  • (Message)

    Each message received

Raises:



416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
# File 'lib/claude_agent_sdk.rb', line 416

def receive_response(&block)
  return enum_for(:receive_response) unless block

  raise CLIConnectionError, 'Not connected. Call connect() first' unless @connected

  # Keep `break` on the same fiber as the underlying dequeue. Going through
  # Client#receive_messages would put the FiberBoundary hop above the break
  # and hang in Client mode — the CLI keeps stdin open and never emits `:end`.
  @query_handler.receive_messages do |data|
    message = MessageParser.parse(data)
    next unless message

    ClaudeAgentSDK.notify_observers(@resolved_observers, :on_message, message)
    FiberBoundary.invoke { block.call(message) }
    break if message.is_a?(ResultMessage)
  end
end

#reconnect_mcp_server(server_name) ⇒ Object

Reconnect a failed MCP server

Parameters:

  • server_name (String)

    Name of the MCP server to reconnect

Raises:



456
457
458
459
# File 'lib/claude_agent_sdk.rb', line 456

def reconnect_mcp_server(server_name)
  raise CLIConnectionError, 'Not connected. Call connect() first' unless @connected
  @query_handler.reconnect_mcp_server(server_name)
end

#rewind_files(user_message_uuid) ⇒ Object

Rewind files to a previous checkpoint (v0.1.15+) Restores file state to what it was at the given user message Requires enable_file_checkpointing to be true in options

Parameters:

  • user_message_uuid (String)

    The UUID of the UserMessage to rewind to

Raises:



480
481
482
483
# File 'lib/claude_agent_sdk.rb', line 480

def rewind_files(user_message_uuid)
  raise CLIConnectionError, 'Not connected. Call connect() first' unless @connected
  @query_handler.rewind_files(user_message_uuid)
end

#server_infoHash?

Get server initialization info

Returns:

  • (Hash, nil)

    Server info or nil



487
488
489
# File 'lib/claude_agent_sdk.rb', line 487

def server_info
  @query_handler&.instance_variable_get(:@initialization_result)
end

#set_model(model) ⇒ Object

Change the AI model during conversation

Parameters:

  • model (String, nil)

    Model name or nil for default

Raises:



449
450
451
452
# File 'lib/claude_agent_sdk.rb', line 449

def set_model(model)
  raise CLIConnectionError, 'Not connected. Call connect() first' unless @connected
  @query_handler.set_model(model)
end

#set_permission_mode(mode) ⇒ Object

Change permission mode during conversation

Parameters:

  • mode (String)

    Permission mode (‘default’, ‘acceptEdits’, ‘bypassPermissions’)

Raises:



442
443
444
445
# File 'lib/claude_agent_sdk.rb', line 442

def set_permission_mode(mode)
  raise CLIConnectionError, 'Not connected. Call connect() first' unless @connected
  @query_handler.set_permission_mode(mode)
end

#stop_task(task_id) ⇒ Object

Stop a running background task

Parameters:

  • task_id (String)

    The ID of the task to stop

Raises:



471
472
473
474
# File 'lib/claude_agent_sdk.rb', line 471

def stop_task(task_id)
  raise CLIConnectionError, 'Not connected. Call connect() first' unless @connected
  @query_handler.stop_task(task_id)
end

#toggle_mcp_server(server_name, enabled) ⇒ Object

Enable or disable an MCP server

Parameters:

  • server_name (String)

    Name of the MCP server

  • enabled (Boolean)

    Whether to enable or disable

Raises:



464
465
466
467
# File 'lib/claude_agent_sdk.rb', line 464

def toggle_mcp_server(server_name, enabled)
  raise CLIConnectionError, 'Not connected. Call connect() first' unless @connected
  @query_handler.toggle_mcp_server(server_name, enabled)
end