cc_sdk/transport/
mod.rs

1//! Transport layer abstractions
2//!
3//! This module defines the Transport trait and its implementations for
4//! communicating with the Claude CLI.
5
6use crate::{
7    errors::Result,
8    types::{ControlRequest, ControlResponse, Message},
9};
10use async_trait::async_trait;
11use futures::stream::Stream;
12use serde_json::Value as JsonValue;
13use std::pin::Pin;
14use tokio::sync::mpsc::Receiver;
15
16pub mod subprocess;
17pub mod mock;
18
19pub use subprocess::SubprocessTransport;
20
21/// Input message structure for sending to Claude
22#[derive(Debug, Clone, serde::Serialize)]
23pub struct InputMessage {
24    /// Message type (always "user")
25    #[serde(rename = "type")]
26    pub r#type: String,
27    /// Message content
28    pub message: serde_json::Value,
29    /// Parent tool use ID (for tool results)
30    pub parent_tool_use_id: Option<String>,
31    /// Session ID
32    pub session_id: String,
33}
34
35impl InputMessage {
36    /// Create a new user message
37    pub fn user(content: String, session_id: String) -> Self {
38        Self {
39            r#type: "user".to_string(),
40            message: serde_json::json!({
41                "role": "user",
42                "content": content
43            }),
44            parent_tool_use_id: None,
45            session_id,
46        }
47    }
48
49    /// Create a tool result message
50    pub fn tool_result(
51        tool_use_id: String,
52        content: String,
53        session_id: String,
54        is_error: bool,
55    ) -> Self {
56        Self {
57            r#type: "user".to_string(),
58            message: serde_json::json!({
59                "role": "user",
60                "content": [{
61                    "type": "tool_result",
62                    "tool_use_id": tool_use_id,
63                    "content": content,
64                    "is_error": is_error
65                }]
66            }),
67            parent_tool_use_id: Some(tool_use_id),
68            session_id,
69        }
70    }
71}
72
73/// Transport trait for communicating with Claude CLI
74#[async_trait]
75pub trait Transport: Send + Sync {
76    /// Get self as Any for downcasting
77    fn as_any_mut(&mut self) -> &mut dyn std::any::Any;
78    
79    /// Connect to the Claude CLI
80    async fn connect(&mut self) -> Result<()>;
81
82    /// Send a message to Claude
83    async fn send_message(&mut self, message: InputMessage) -> Result<()>;
84
85    /// Receive messages from Claude as a stream
86    fn receive_messages(&mut self) -> Pin<Box<dyn Stream<Item = Result<Message>> + Send + 'static>>;
87
88    /// Send a control request (e.g., interrupt)
89    async fn send_control_request(&mut self, request: ControlRequest) -> Result<()>;
90
91    /// Receive control responses
92    async fn receive_control_response(&mut self) -> Result<Option<ControlResponse>>;
93    
94    /// Send an SDK control request (for control protocol)
95    async fn send_sdk_control_request(&mut self, request: JsonValue) -> Result<()>;
96    
97    /// Send an SDK control response
98    async fn send_sdk_control_response(&mut self, response: JsonValue) -> Result<()>;
99
100    /// Take the SDK control receiver, if supported by the transport
101    /// Default implementation returns None for transports that do not
102    /// support inbound control messages.
103    fn take_sdk_control_receiver(&mut self) -> Option<Receiver<JsonValue>> {
104        None
105    }
106
107    /// Check if the transport is connected
108    #[allow(dead_code)]
109    fn is_connected(&self) -> bool;
110
111    /// Disconnect from the Claude CLI
112    async fn disconnect(&mut self) -> Result<()>;
113
114    /// Signal end of input stream (default: no-op)
115    async fn end_input(&mut self) -> Result<()> {
116        Ok(())
117    }
118}
119
120/// Transport state
121#[derive(Debug, Clone, Copy, PartialEq, Eq)]
122pub enum TransportState {
123    /// Not connected
124    Disconnected,
125    /// Connecting
126    Connecting,
127    /// Connected and ready
128    Connected,
129    /// Disconnecting
130    Disconnecting,
131    /// Error state
132    #[allow(dead_code)]
133    Error,
134}
135
136#[cfg(test)]
137mod tests {
138    use super::*;
139
140    #[test]
141    fn test_input_message_user() {
142        let msg = InputMessage::user("Hello".to_string(), "session-123".to_string());
143        assert_eq!(msg.r#type, "user");
144        assert_eq!(msg.session_id, "session-123");
145        assert!(msg.parent_tool_use_id.is_none());
146
147        let json = serde_json::to_string(&msg).unwrap();
148        assert!(json.contains(r#""type":"user""#));
149        assert!(json.contains(r#""content":"Hello""#));
150    }
151
152    #[test]
153    fn test_input_message_tool_result() {
154        let msg = InputMessage::tool_result(
155            "tool-123".to_string(),
156            "Result".to_string(),
157            "session-456".to_string(),
158            false,
159        );
160        assert_eq!(msg.r#type, "user");
161        assert_eq!(msg.parent_tool_use_id, Some("tool-123".to_string()));
162
163        let json = serde_json::to_string(&msg).unwrap();
164        assert!(json.contains(r#""tool_use_id":"tool-123""#));
165        assert!(json.contains(r#""is_error":false"#));
166    }
167}