claude_code/transport/mod.rs
1//! Transport layer abstraction for CLI communication.
2//!
3//! This module defines the [`Transport`] trait, which abstracts the communication
4//! channel between the SDK and the Claude Code CLI process. The default implementation
5//! is [`SubprocessCliTransport`](subprocess_cli::SubprocessCliTransport), which spawns
6//! the CLI as a child process and communicates via stdin/stdout.
7//!
8//! Custom transports can be implemented for testing or alternative communication
9//! mechanisms by implementing the [`Transport`] trait.
10//!
11//! # Split I/O
12//!
13//! For concurrent read/write scenarios, the transport can be split into independent
14//! reader and writer halves via [`Transport::into_split()`]. Implementations can use
15//! [`split_with_adapter()`] for a simple lock-based fallback via [`SplitAdapter`].
16
17use std::sync::Arc;
18
19use async_trait::async_trait;
20use serde_json::Value;
21use tokio::sync::Mutex;
22
23use crate::errors::Result;
24
25pub mod subprocess_cli;
26
27/// The result of splitting a transport into independent reader, writer, and close handle.
28pub type TransportSplitResult = Result<(
29 Box<dyn TransportReader>,
30 Box<dyn TransportWriter>,
31 Box<dyn TransportCloseHandle>,
32)>;
33
34/// Async reader half of a split transport.
35///
36/// Reads JSON messages from the CLI's output stream independently of writes.
37#[async_trait]
38pub trait TransportReader: Send {
39 /// Reads the next JSON message from the CLI's output stream.
40 ///
41 /// Returns `Ok(None)` when the stream is exhausted (EOF).
42 async fn read_next_message(&mut self) -> Result<Option<Value>>;
43}
44
45/// Async writer half of a split transport.
46///
47/// Writes data to the CLI's input stream independently of reads.
48#[async_trait]
49pub trait TransportWriter: Send {
50 /// Writes a string (typically a JSON line) to the CLI's input stream.
51 async fn write(&mut self, data: &str) -> Result<()>;
52
53 /// Signals that no more input will be sent (closes the input stream).
54 async fn end_input(&mut self) -> Result<()>;
55}
56
57/// Async transport trait for bidirectional communication with the Claude Code CLI.
58///
59/// Implementations handle the lifecycle of the communication channel: connecting,
60/// writing JSON messages, reading responses, and closing the channel.
61///
62/// All methods are async and the trait requires `Send` for use in async runtimes.
63///
64/// # Splitting
65///
66/// After [`connect()`](Transport::connect), call [`into_split()`](Transport::into_split)
67/// to obtain independent reader and writer halves for concurrent I/O. Use
68/// [`split_with_adapter()`] for a lock-based fallback, or provide a native split
69/// for true concurrent I/O.
70#[async_trait]
71pub trait Transport: Send {
72 /// Establishes the transport connection (e.g., spawns the subprocess).
73 async fn connect(&mut self) -> Result<()>;
74
75 /// Writes a string (typically a JSON line) to the CLI's input stream.
76 async fn write(&mut self, data: &str) -> Result<()>;
77
78 /// Signals that no more input will be sent (closes the input stream).
79 async fn end_input(&mut self) -> Result<()>;
80
81 /// Reads the next JSON message from the CLI's output stream.
82 ///
83 /// Returns `Ok(None)` when the stream is exhausted (EOF).
84 async fn read_next_message(&mut self) -> Result<Option<Value>>;
85
86 /// Closes the transport connection and cleans up resources.
87 async fn close(&mut self) -> Result<()>;
88
89 /// Returns `true` if the transport is connected and ready for I/O.
90 fn is_ready(&self) -> bool;
91
92 /// Splits the transport into independent reader and writer halves.
93 ///
94 /// Consumes the transport. The returned halves can be used concurrently
95 /// from different tasks.
96 ///
97 /// For a simple implementation, delegate to [`split_with_adapter(self)`](split_with_adapter)
98 /// which wraps `self` in a [`SplitAdapter`] that serializes access via a mutex.
99 /// Transports that can provide true concurrent I/O (like
100 /// [`SubprocessCliTransport`](subprocess_cli::SubprocessCliTransport)) should
101 /// provide a native split instead.
102 ///
103 /// # Returns
104 ///
105 /// A tuple of `(reader, writer, close_handle)`. The `close_handle` should
106 /// be used to close the transport and clean up resources when done.
107 fn into_split(self: Box<Self>) -> TransportSplitResult;
108}
109
110/// Handle for closing a transport after it has been split.
111///
112/// Holds shared ownership of the transport and provides async cleanup.
113#[async_trait]
114pub trait TransportCloseHandle: Send + Sync {
115 /// Closes the transport and cleans up all resources.
116 async fn close(&self) -> Result<()>;
117}
118
119/// Factory for creating fresh [`Transport`] instances.
120///
121/// Used by [`ClaudeSdkClient`](crate::ClaudeSdkClient) to produce a new transport
122/// on each [`connect()`](crate::ClaudeSdkClient::connect) call, enabling reconnect
123/// after disconnect without consuming the factory.
124///
125/// # Example
126///
127/// ```rust,ignore
128/// use claude_code::{Transport, TransportFactory, Result};
129///
130/// struct MyTransportFactory { /* config */ }
131///
132/// impl TransportFactory for MyTransportFactory {
133/// fn create_transport(&self) -> Result<Box<dyn Transport>> {
134/// Ok(Box::new(MyTransport::new()))
135/// }
136/// }
137/// ```
138pub trait TransportFactory: Send + Sync {
139 /// Creates a new transport instance for a new connection session.
140 fn create_transport(&self) -> Result<Box<dyn Transport>>;
141}
142
143/// Splits a transport using a lock-based adapter.
144///
145/// This is a convenience function for implementing [`Transport::into_split()`]
146/// when a transport doesn't have a natural way to split its I/O. All operations
147/// are serialized via a mutex.
148///
149/// # Example
150///
151/// ```rust
152/// use claude_code::transport::subprocess_cli::{Prompt, SubprocessCliTransport};
153/// use claude_code::transport::split_with_adapter;
154///
155/// let transport = SubprocessCliTransport::new(Prompt::Messages, Default::default()).unwrap();
156/// let (_reader, _writer, _close) = split_with_adapter(Box::new(transport)).unwrap();
157/// ```
158pub fn split_with_adapter(transport: Box<dyn Transport>) -> TransportSplitResult {
159 let adapter = SplitAdapter::new(transport);
160 Ok((
161 Box::new(adapter.reader()),
162 Box::new(adapter.writer()),
163 Box::new(adapter),
164 ))
165}
166
167/// Lock-based split adapter for backward-compatible transport splitting.
168///
169/// Wraps a `Box<dyn Transport>` in `Arc<Mutex<>>` and provides reader/writer
170/// halves that serialize access. This is the fallback for transports that
171/// choose not to provide a native split.
172pub struct SplitAdapter {
173 inner: Arc<Mutex<Box<dyn Transport>>>,
174}
175
176impl SplitAdapter {
177 /// Creates a new `SplitAdapter` wrapping the given transport.
178 ///
179 /// # Example
180 ///
181 /// ```rust
182 /// use claude_code::transport::subprocess_cli::{Prompt, SubprocessCliTransport};
183 /// use claude_code::transport::SplitAdapter;
184 ///
185 /// let transport = SubprocessCliTransport::new(Prompt::Messages, Default::default()).unwrap();
186 /// let _adapter = SplitAdapter::new(Box::new(transport));
187 /// ```
188 pub fn new(transport: Box<dyn Transport>) -> Self {
189 Self {
190 inner: Arc::new(Mutex::new(transport)),
191 }
192 }
193
194 /// Returns a reader half backed by the shared transport.
195 ///
196 /// # Example
197 ///
198 /// ```rust
199 /// use claude_code::transport::subprocess_cli::{Prompt, SubprocessCliTransport};
200 /// use claude_code::transport::SplitAdapter;
201 ///
202 /// let transport = SubprocessCliTransport::new(Prompt::Messages, Default::default()).unwrap();
203 /// let adapter = SplitAdapter::new(Box::new(transport));
204 /// let _reader = adapter.reader();
205 /// ```
206 pub fn reader(&self) -> SplitAdapterReader {
207 SplitAdapterReader {
208 inner: self.inner.clone(),
209 }
210 }
211
212 /// Returns a writer half backed by the shared transport.
213 ///
214 /// # Example
215 ///
216 /// ```rust
217 /// use claude_code::transport::subprocess_cli::{Prompt, SubprocessCliTransport};
218 /// use claude_code::transport::SplitAdapter;
219 ///
220 /// let transport = SubprocessCliTransport::new(Prompt::Messages, Default::default()).unwrap();
221 /// let adapter = SplitAdapter::new(Box::new(transport));
222 /// let _writer = adapter.writer();
223 /// ```
224 pub fn writer(&self) -> SplitAdapterWriter {
225 SplitAdapterWriter {
226 inner: self.inner.clone(),
227 }
228 }
229}
230
231#[async_trait]
232impl TransportCloseHandle for SplitAdapter {
233 async fn close(&self) -> Result<()> {
234 self.inner.lock().await.close().await
235 }
236}
237
238/// Reader half of a [`SplitAdapter`].
239pub struct SplitAdapterReader {
240 inner: Arc<Mutex<Box<dyn Transport>>>,
241}
242
243#[async_trait]
244impl TransportReader for SplitAdapterReader {
245 async fn read_next_message(&mut self) -> Result<Option<Value>> {
246 self.inner.lock().await.read_next_message().await
247 }
248}
249
250/// Writer half of a [`SplitAdapter`].
251pub struct SplitAdapterWriter {
252 inner: Arc<Mutex<Box<dyn Transport>>>,
253}
254
255#[async_trait]
256impl TransportWriter for SplitAdapterWriter {
257 async fn write(&mut self, data: &str) -> Result<()> {
258 self.inner.lock().await.write(data).await
259 }
260
261 async fn end_input(&mut self) -> Result<()> {
262 self.inner.lock().await.end_input().await
263 }
264}