pub struct Client {
inner: Arc<ClientInner>,
}Expand description
An active claude binary subprocess.
Construct via spawn. The init handshake
(initialize control_request + drained response) runs inside
spawn; the system/init frame is consumed and its session id
cached on session_id(). The returned
ClientEvents receiver yields the clean conversational stream
starting with hook/assistant/user frames.
Client is Clone. All clones share the same underlying
subprocess, reader task, and pending-control map. Cloning is
cheap (an Arc increment); pass Client by value or reference
freely.
Fields§
§inner: Arc<ClientInner>Implementations§
Source§impl Client
impl Client
Sourcepub async fn interrupt(&self) -> Result<(), Error>
pub async fn interrupt(&self) -> Result<(), Error>
Ask the CLI to interrupt the current turn (cancel in-flight tool calls and return control to the SDK).
§Errors
See the outbound control error cases.
Sourcepub async fn cancel_queued_message(
&self,
message_uuid: &str,
) -> Result<bool, Error>
pub async fn cancel_queued_message( &self, message_uuid: &str, ) -> Result<bool, Error>
Drop one prompt still sitting in the CLI’s queue, by the uuid it was sent under.
Answers whether the CLI cancelled it. false means it was not in the
queue - already dequeued for execution, or never enqueued - and the
prompt is on its way to the model regardless; a malformed or missing
answer reads the same way (false), which fails closed: the prompt stays
queued rather than a caller believing it was dropped.
§Errors
See the outbound control error cases.
Sourcepub async fn set_permission_mode(
&self,
mode: PermissionMode,
) -> Result<(), Error>
pub async fn set_permission_mode( &self, mode: PermissionMode, ) -> Result<(), Error>
Sourcepub async fn set_model(&self, model: Option<&str>) -> Result<(), Error>
pub async fn set_model(&self, model: Option<&str>) -> Result<(), Error>
Switch the model mid-session. Pass Some("claude-sonnet-4-6") or
similar to pick a specific model; None reverts to the CLI
default. Wire shape: {"subtype": "set_model", "model": <string or null>}.
§Errors
See the outbound control error cases.
Sourcepub async fn mcp_reconnect(&self, server_name: &str) -> Result<(), Error>
pub async fn mcp_reconnect(&self, server_name: &str) -> Result<(), Error>
Reconnect a named MCP server (asks the CLI to drop + re-establish
its connection to the named server). Wire shape uses camelCase
serverName.
§Errors
See the outbound control error cases.
Sourcepub async fn mcp_toggle(
&self,
server_name: &str,
enabled: bool,
) -> Result<(), Error>
pub async fn mcp_toggle( &self, server_name: &str, enabled: bool, ) -> Result<(), Error>
Toggle a named MCP server on/off. Wire shape uses camelCase
serverName.
§Errors
See the outbound control error cases.
Sourcepub async fn mcp_set_servers(
&self,
servers: Value,
) -> Result<McpSetServersResponse, Error>
pub async fn mcp_set_servers( &self, servers: Value, ) -> Result<McpSetServersResponse, Error>
Replace the CLI’s dynamically managed MCP server set. Sdk servers
are entries of {"type":"sdk","name":<n>}; the CLI re-handshakes
everything it adds, so this is the one lever that revives an sdk
server whose boot-time handshake was abandoned (the CLI reports
such a server connected with zero tools and never re-runs the
handshake itself).
§Errors
See the outbound control error cases, plus Error::MessageParse
when the CLI payload doesn’t match
McpSetServersResponse.
Sourcepub async fn stop_task(&self, task_id: &str) -> Result<(), Error>
pub async fn stop_task(&self, task_id: &str) -> Result<(), Error>
Kill an in-flight sub-agent task by its task_id (from the
TaskStarted system message).
§Errors
See the outbound control error cases.
Sourcepub async fn mcp_status(&self) -> Result<McpStatusResponse, Error>
pub async fn mcp_status(&self) -> Result<McpStatusResponse, Error>
Query MCP server status. Returns the typed response.
§Errors
See the outbound control error cases, plus Error::MessageParse
when the CLI payload doesn’t match
McpStatusResponse.
Sourcepub async fn get_context_usage(&self) -> Result<ContextUsageResponse, Error>
pub async fn get_context_usage(&self) -> Result<ContextUsageResponse, Error>
Query current context usage (tokens consumed vs. budget). Returns the typed response.
§Errors
See the outbound control error cases, plus Error::MessageParse
when the CLI payload doesn’t match
ContextUsageResponse.
Sourcepub async fn reload_plugins(&self) -> Result<Value, Error>
pub async fn reload_plugins(&self) -> Result<Value, Error>
Ask the CLI to reload session plugins (slash commands, agents,
MCP servers). Wire shape: {"subtype": "reload_plugins"}. The
CLI replies with the refreshed plugin inventory; returns the
raw JSON for the agent layer to forward.
§Errors
See the outbound control error cases.
Source§impl Client
impl Client
Sourcepub async fn spawn(options: Options) -> Result<(Self, ClientEvents), Error>
pub async fn spawn(options: Options) -> Result<(Self, ClientEvents), Error>
Spawn claude with the given options and run the init handshake.
Wire-recording for conformance baselines is configured via
Options::tee_inbound /
Options::tee_outbound callbacks - the
forge-test-harness crate drives this.
§Errors
Any Error variant; see field docs.
Sourcepub fn get_server_info(&self) -> Option<&Value>
pub fn get_server_info(&self) -> Option<&Value>
Cached response from the initialize control_request - holds
the CLI’s server capabilities, available commands, and output
styles. Returns None when the CLI didn’t attach a body to its
initialize response.
Sourcepub fn claude_pid(&self) -> Option<u32>
pub fn claude_pid(&self) -> Option<u32>
OS-level PID of the spawned claude child, captured at spawn
time. Stable for the lifetime of this Client. Used by
downstream consumers (e.g. forge_agent::env::processes) to
walk the child process tree for the Inspector pane’s
PROCESSES section.
Returns None only when the transport couldn’t report a PID
at spawn - never happens for a real subprocess on supported
platforms, but the Option keeps the test-stub path
expressible.
Sourcepub fn kill_process_group(&self)
pub fn kill_process_group(&self)
SIGKILL the child’s process group now, without the reader-task drain
disconnect performs.
For a session that has no task to run its exit path: dropping the handle alone kills only the direct child and leaves whatever the CLI already started behind.
Sourcepub fn initial_session_data(&self) -> Option<&Value>
pub fn initial_session_data(&self) -> Option<&Value>
Captured system/init payload - the CLI’s first session-scoped
frame, carrying model, tools, mcp_servers, slash_commands,
agents, skills, etc. Stripped from the user-visible Message
stream during init; cached here for callers that need the
initial session context (e.g. forge-agent’s status snapshot +
slash autocomplete).
Sourcepub fn session_id(&self) -> String
pub fn session_id(&self) -> String
The session id captured from the init message. Returns an empty string until the first session-scoped frame arrives.
Sourcepub fn account_info_from_init(&self) -> Option<AccountInfo>
pub fn account_info_from_init(&self) -> Option<AccountInfo>
Read the bare apiKeySource field from the cached
system/init payload, returning a partial
forge_primitives::AccountInfo (auth source only - no
email/org/subscription). Returns None when the payload is
absent (init not yet arrived), the field is missing, or the
value is empty / "none".
Sourcepub async fn send_user_message_under(
&self,
prompt: &str,
uuid: &str,
) -> Result<(), Error>
pub async fn send_user_message_under( &self, prompt: &str, uuid: &str, ) -> Result<(), Error>
Send a user prompt under an id the caller minted.
Use this when the id must be known before the write - a dispatcher
that draws the prompt’s row and reconciles against the lifecycle
frames by id. Mint with
crate::request_id::next_prompt_id and never reuse one: the CLI
drops a repeated id’s prompt silently.
§Errors
Error::Io on pipe write failure;
Error::MessageParse on JSON serialization failure.
Sourcepub async fn send_user_message_with_content(
&self,
content: &[Value],
) -> Result<String, Error>
pub async fn send_user_message_with_content( &self, content: &[Value], ) -> Result<String, Error>
Send a user prompt with structured content blocks (text +
image, etc.) as a stream-json user turn. Use this when the
prompt is multi-modal; for plain text use
send_user_message.
content is forwarded verbatim as the message body’s
content field - callers must build CLI-compatible block
objects (e.g. {"type":"text","text":"..."},
{"type":"image","source":{"type":"base64","media_type":"image/png","data":"..."}}).
Mints and returns the prompt’s id, as Self::send_user_message does.
§Errors
Error::Io on pipe write failure;
Error::MessageParse on JSON serialization failure.
Sourcepub async fn send_user_message_with_content_under(
&self,
content: &[Value],
uuid: &str,
) -> Result<(), Error>
pub async fn send_user_message_with_content_under( &self, content: &[Value], uuid: &str, ) -> Result<(), Error>
Send structured content under an id the caller minted, as
Self::send_user_message_under does.
§Errors
Error::Io on pipe write failure;
Error::MessageParse on JSON serialization failure.
Sourcepub async fn disconnect(&self) -> Result<(), Error>
pub async fn disconnect(&self) -> Result<(), Error>
Graceful shutdown. Signals the reader task to stop, waits for it to drain and close the subprocess. Idempotent - subsequent calls (on this clone or any other) are no-ops.
§Errors
None today; the inner reader-task close is best-effort. Kept
Result for forward-compat.
Sourcepub fn disconnect_requested(&self) -> bool
pub fn disconnect_requested(&self) -> bool
Whether disconnect has been called on any
clone of this client. Lets a consumer distinguish an expected
stream end (session swap, teardown) from the subprocess dying
on its own.
Sourcepub(crate) async fn send_control(
&self,
subtype: &str,
extra: Value,
) -> Result<Value, Error>
pub(crate) async fn send_control( &self, subtype: &str, extra: Value, ) -> Result<Value, Error>
Internal: outbound control_request issuer. Inserts a oneshot
into pending_controls, writes the envelope via the cloned
writer, and awaits the reader task’s routed response.
If the awaiting future is dropped - a caller’s timeout fired, or
the task died - the oneshot sender would otherwise sit in
pending_controls forever, one leak per dropped call (the
footer’s context-usage poll being the unbounded one). The
PendingGuard returned in the future removes the entry whenever
the future is dropped without a response having been routed; a
late response then finds no entry, which the reader task already
handles.