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)



439
440
441
442
443
444
445
446
447
# File 'lib/claude_agent_sdk.rb', line 439

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
  @materialized = nil
end

Instance Attribute Details

#query_handlerObject (readonly)

Returns the value of attribute query_handler.



433
434
435
# File 'lib/claude_agent_sdk.rb', line 433

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)


457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
# File 'lib/claude_agent_sdk.rb', line 457

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

  # Fail fast on invalid session_store combinations before spawning the CLI.
  SessionStores.validate_session_store_options(configured_options)

  # Resume-from-store: materialize the session from the store into a temp
  # CLAUDE_CONFIG_DIR BEFORE spawn, then repoint options at it. Skipped for
  # custom transports (the materialized env/--resume only apply to the CLI
  # subprocess). On any later connect failure, disconnect cleans up the dir.
  configured_options = materialize_resume(configured_options)

  # If anything after materialization fails, tear down (closes the
  # subprocess and removes the materialized temp config dir) before
  # surfacing the error, so a partial connect never leaks a temp dir
  # holding a credential copy.
  begin
    connect_inner(configured_options, prompt)
  rescue Exception # rubocop:disable Lint/RescueException
    # Tear down the partial connect, but never let a cleanup failure (e.g. a
    # custom transport whose #close raises) mask the original connect error.
    # Rescue Exception (not StandardError) so reactor cancellation
    # (Async::Stop < Exception) after materialize_resume set @materialized
    # still runs disconnect -> @materialized.cleanup, never leaking the temp
    # CLAUDE_CONFIG_DIR that holds the redacted .credentials.json copy.
    begin
      disconnect
    rescue StandardError => e
      warn "Claude SDK: cleanup after failed connect raised: #{e.message}"
    end
    raise
  end
end

#disconnectObject

Disconnect from Claude



636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
# File 'lib/claude_agent_sdk.rb', line 636

def disconnect
  ClaudeAgentSDK.notify_observers(@resolved_observers || [], :on_close) if @connected
  # Tear down whatever exists — robust to a partial/failed connect, where
  # @connected is still false but a transport and/or materialized temp dir
  # were already created. #close on the query handler also closes the
  # transport (flushing the mirror batcher first); the extra @transport
  # close covers a failure before the query handler was built (idempotent).
  #
  # The nested ensures guarantee that even a raising close (e.g. a custom
  # transport whose #close raises) still runs the transport close, resets
  # state, and removes the materialized temp dir (which holds a redacted
  # .credentials.json copy) — so disconnect can never leave the client
  # half-open or leak the temp dir. The original error still propagates.
  begin
    @query_handler&.close
  ensure
    @query_handler = nil
    begin
      @transport&.close
    ensure
      @transport = nil
      @connected = false
      # Remove the materialized resume temp dir AFTER the subprocess exited.
      if @materialized
        @materialized.cleanup
        @materialized = nil
      end
    end
  end
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:



616
617
618
619
# File 'lib/claude_agent_sdk.rb', line 616

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:



623
624
625
626
# File 'lib/claude_agent_sdk.rb', line 623

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:



630
631
632
633
# File 'lib/claude_agent_sdk.rb', line 630

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

#interruptObject

Send interrupt signal

Raises:



556
557
558
559
# File 'lib/claude_agent_sdk.rb', line 556

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:



506
507
508
509
510
511
512
513
514
515
516
517
# File 'lib/claude_agent_sdk.rb', line 506

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:



521
522
523
524
525
526
527
528
529
530
531
532
533
# File 'lib/claude_agent_sdk.rb', line 521

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:



537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
# File 'lib/claude_agent_sdk.rb', line 537

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:



577
578
579
580
# File 'lib/claude_agent_sdk.rb', line 577

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:



601
602
603
604
# File 'lib/claude_agent_sdk.rb', line 601

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



608
609
610
# File 'lib/claude_agent_sdk.rb', line 608

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:



570
571
572
573
# File 'lib/claude_agent_sdk.rb', line 570

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:



563
564
565
566
# File 'lib/claude_agent_sdk.rb', line 563

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:



592
593
594
595
# File 'lib/claude_agent_sdk.rb', line 592

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:



585
586
587
588
# File 'lib/claude_agent_sdk.rb', line 585

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