protocol

package
v0.0.5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 13, 2026 License: GPL-3.0 Imports: 16 Imported by: 0

Documentation

Overview

Package protocol implements bidirectional control message handling for the Claude CLI.

The protocol package provides a Controller that manages request/response correlation for control messages sent to and received from the Claude CLI. This enables interactive features like interruption, dynamic configuration changes, and hook callbacks.

The Controller handles:

  • Sending control_request messages with unique IDs
  • Receiving and correlating control_response messages
  • Request timeout enforcement
  • Handler registration for incoming requests from the CLI

Example usage:

transport := subprocess.NewCLITransport(log, "", options)
transport.Start(ctx)

controller := protocol.NewController(log, transport)
controller.Start(ctx)

// Send a request with timeout
resp, err := controller.SendRequest(ctx, "interrupt", nil, 5*time.Second)

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type ControlRequest

type ControlRequest struct {
	// Type is always "control_request"
	Type string `json:"type"`

	// RequestID uniquely identifies this request for response correlation
	RequestID string `json:"request_id"` //nolint:tagliatelle // Claude CLI uses snake_case

	// Request contains the nested request data including subtype and payload fields
	Request map[string]any `json:"request"`
}

ControlRequest represents a control message sent to or received from the CLI.

Wire format:

{
  "type": "control_request",
  "request_id": "req_1_abc123",
  "request": {
    "subtype": "initialize",
    "hooks": {...}
  }
}

func (*ControlRequest) Subtype

func (r *ControlRequest) Subtype() string

Subtype extracts the subtype from the nested request data.

type ControlResponse

type ControlResponse struct {
	// Type is always "control_response"
	Type string `json:"type"`

	// Response contains the nested response data including subtype, request_id,
	// and either response (for success) or error (for error)
	Response map[string]any `json:"response"`
}

ControlResponse represents a response to a control request.

Wire format for success:

{
  "type": "control_response",
  "response": {
    "subtype": "success",
    "request_id": "req_1_abc123",
    "response": {...}
  }
}

Wire format for error:

{
  "type": "control_response",
  "response": {
    "subtype": "error",
    "request_id": "req_1_abc123",
    "error": "error message"
  }
}

func (*ControlResponse) ErrorMessage

func (r *ControlResponse) ErrorMessage() string

ErrorMessage extracts the error message from an error response.

func (*ControlResponse) IsError

func (r *ControlResponse) IsError() bool

IsError checks if the response is an error response.

func (*ControlResponse) Payload

func (r *ControlResponse) Payload() map[string]any

Payload extracts the response payload from a success response.

func (*ControlResponse) RequestID

func (r *ControlResponse) RequestID() string

RequestID extracts the request_id from the nested response.

type Controller

type Controller struct {
	// contains filtered or unexported fields
}

Controller manages bidirectional control message communication with the Claude CLI.

The Controller handles:

  • Sending control_request messages with unique request IDs
  • Receiving and routing control_response messages to waiting requests
  • Request timeout enforcement
  • Handler registration for incoming control_request messages from the CLI
  • Forwarding non-control messages to consumers via the Messages channel

The Controller must be started with Start() before use and manages its own goroutine for reading and routing messages.

func NewController

func NewController(log *slog.Logger, transport Transport) *Controller

NewController creates a new protocol controller.

The logger will receive debug, info, warn, and error messages during protocol operations. The transport must be connected before calling Start().

func (*Controller) CancelAllInFlight

func (c *Controller) CancelAllInFlight()

CancelAllInFlight cancels all in-flight operations. This is called during Stop() to ensure clean shutdown.

func (*Controller) Done

func (c *Controller) Done() <-chan struct{}

Done returns a channel that is closed when the controller stops.

func (*Controller) FatalError

func (c *Controller) FatalError() error

FatalError returns the fatal error if one occurred.

func (*Controller) Messages

func (c *Controller) Messages() <-chan map[string]any

Messages returns a channel for receiving non-control messages.

The controller acts as a multiplexer: it reads all messages from the transport, handles control messages internally, and forwards regular messages through this channel. Consumers should read from this channel instead of calling transport.ReadMessages() directly.

The channel is closed when the controller stops or the transport closes. Use Done() and FatalError() to detect and retrieve transport errors.

func (*Controller) RegisterHandler

func (c *Controller) RegisterHandler(subtype string, handler RequestHandler)

RegisterHandler registers a handler for incoming control requests.

When the CLI sends a control_request with the specified subtype, the handler will be invoked. The handler should return a payload map or an error.

Only one handler can be registered per subtype. Registering a handler for the same subtype twice will override the previous handler.

func (*Controller) SendRequest

func (c *Controller) SendRequest(
	ctx context.Context,
	subtype string,
	payload map[string]any,
	timeout time.Duration,
) (*ControlResponse, error)

SendRequest sends a control request and waits for the response.

This method generates a unique request ID, sends the control_request, and blocks until a matching control_response is received or the timeout expires.

The timeout parameter specifies how long to wait for a response. Use context cancellation for overall operation timeout.

Returns an error if the request fails to send, times out, or the CLI returns an error response.

func (*Controller) SetFatalError

func (c *Controller) SetFatalError(err error)

SetFatalError stores a fatal error and broadcasts to all waiters by closing done.

func (*Controller) Start

func (c *Controller) Start(ctx context.Context) error

Start begins reading messages from the transport and routing control messages.

This method spawns a goroutine that reads from the transport and routes control_request and control_response messages. The goroutine stops when the context is cancelled or the transport is closed.

Start must be called before SendRequest or any handlers will work.

func (*Controller) Stop

func (c *Controller) Stop()

Stop gracefully shuts down the controller.

This method signals the read loop to stop, cancels all in-flight operations, and waits for completion. It's safe to call Stop multiple times.

type RequestHandler

type RequestHandler func(ctx context.Context, req *ControlRequest) (map[string]any, error)

RequestHandler is a function that handles incoming control requests from the CLI.

Handlers are registered for specific subtypes and are invoked when the CLI sends a control_request (e.g., for hook callbacks).

The handler should return a payload map or an error. The Controller automatically wraps the result in a ControlResponse.

type Session

type Session struct {
	// contains filtered or unexported fields
}

Session encapsulates protocol handling logic for hooks, MCP servers, and callbacks. It can be used by both Client and Query() to provide protocol support.

func NewSession

func NewSession(
	log *slog.Logger,
	controller *Controller,
	options *config.Options,
) *Session

NewSession creates a new Session for protocol handling.

func (*Session) GetInitializationResult

func (s *Session) GetInitializationResult() map[string]any

GetInitializationResult returns a copy of the server initialization info. Returns nil if not initialized.

func (*Session) GetSDKMCPServer added in v0.0.2

func (s *Session) GetSDKMCPServer(name string) (mcp.ServerInstance, bool)

GetSDKMCPServer returns a registered SDK MCP server by name.

func (*Session) GetSDKMCPServerNames

func (s *Session) GetSDKMCPServerNames() []string

GetSDKMCPServerNames returns the names of all registered in-process SDK MCP servers.

func (*Session) HandleCanUseTool

func (s *Session) HandleCanUseTool(
	ctx context.Context,
	req *ControlRequest,
) (map[string]any, error)

HandleCanUseTool is called by CLI before tool use.

func (*Session) HandleHookCallback

func (s *Session) HandleHookCallback(
	ctx context.Context,
	req *ControlRequest,
) (map[string]any, error)

HandleHookCallback handles hook_callback control requests from the CLI. The CLI sends callback_id which we use to look up the registered callback.

func (*Session) HandleMCPMessage

func (s *Session) HandleMCPMessage(
	ctx context.Context,
	req *ControlRequest,
) (map[string]any, error)

HandleMCPMessage handles unified mcp_message control requests from the CLI. Routes based on the JSONRPC method field: initialize, tools/list, tools/call, notifications/initialized.

func (*Session) Initialize

func (s *Session) Initialize(ctx context.Context) error

Initialize sends the initialization control request to the CLI. It generates callback IDs for each hook and stores them for later lookup.

func (*Session) NeedsInitialization

func (s *Session) NeedsInitialization() bool

NeedsInitialization returns true if the session has callbacks that require initialization.

func (*Session) RegisterHandlers

func (s *Session) RegisterHandlers()

RegisterHandlers registers protocol handlers for hooks, MCP, and tool permissions. This must be called before Initialize().

func (*Session) RegisterMCPServers

func (s *Session) RegisterMCPServers()

RegisterMCPServers extracts and registers SDK MCP servers from options.

type Transport

type Transport interface {
	ReadMessages(ctx context.Context) (<-chan map[string]any, <-chan error)
	SendMessage(ctx context.Context, data []byte) error
}

Transport defines the minimal interface needed for protocol operations.

This interface is satisfied by the CLITransport but allows for testing with mock transports.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL