Skip to main content

claude_agent_sdk/control/
protocol.rs

1//! Control protocol implementation for bidirectional communication
2//!
3//! This module provides the protocol handler and message types for the control
4//! protocol used in bidirectional communication with Claude Code CLI.
5//!
6//! # Overview
7//!
8//! The control protocol enables:
9//! - Request/response communication
10//! - Hook invocations from CLI to SDK
11//! - Permission requests from CLI to SDK
12//! - Protocol initialization and capability negotiation
13//!
14//! # Example: Basic Protocol Usage
15//!
16//! ```rust
17//! use claude_agent_sdk::control::ProtocolHandler;
18//!
19//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
20//! let handler = ProtocolHandler::new();
21//!
22//! // Create an initialization request
23//! let init_req = handler.create_init_request();
24//! assert_eq!(init_req.protocol_version, "1.0");
25//!
26//! // After receiving init response, mark as initialized
27//! handler.set_initialized(true);
28//!
29//! // Create control requests
30//! let interrupt_req = handler.create_interrupt_request();
31//! let msg_req = handler.create_send_message_request("Hello!".to_string());
32//! # Ok(())
33//! # }
34//! ```
35//!
36//! # Example: Handling Hook Events
37//!
38//! ```rust
39//! use claude_agent_sdk::control::ProtocolHandler;
40//! use tokio::sync::mpsc;
41//!
42//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
43//! let mut handler = ProtocolHandler::new();
44//!
45//! // Set up hook channel
46//! let (hook_tx, mut hook_rx) = mpsc::unbounded_channel();
47//! handler.set_hook_channel(hook_tx);
48//!
49//! // When a hook event arrives, it will be sent to hook_rx
50//! // You can then process it and send a response
51//! tokio::spawn(async move {
52//!     while let Some((hook_id, event)) = hook_rx.recv().await {
53//!         println!("Received hook: {} {:?}", hook_id, event);
54//!         // Process hook and create response...
55//!     }
56//! });
57//! # Ok(())
58//! # }
59//! ```
60//!
61//! # Example: Serialization
62//!
63//! ```rust
64//! use claude_agent_sdk::control::{ControlMessage, ProtocolHandler};
65//!
66//! # fn example() -> Result<(), Box<dyn std::error::Error>> {
67//! let handler = ProtocolHandler::new();
68//! let request = handler.create_interrupt_request();
69//! let message = ControlMessage::Request(request);
70//!
71//! // Serialize to JSON
72//! let json = handler.serialize_message(&message)?;
73//! assert!(json.ends_with('\n'));
74//!
75//! // Deserialize from JSON
76//! let parsed = handler.deserialize_message(json.trim())?;
77//! # Ok(())
78//! # }
79//! ```
80
81use serde::{Deserialize, Serialize};
82use std::collections::HashMap;
83use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
84use std::sync::Arc;
85use tokio::sync::{mpsc, oneshot, Mutex};
86
87use crate::error::{ClaudeError, Result};
88use crate::types::{HookEvent, PermissionRequest, PermissionResult, RequestId};
89
90/// Control message envelope for all protocol messages
91#[derive(Debug, Clone, Serialize, Deserialize)]
92#[serde(tag = "type")]
93pub enum ControlMessage {
94    /// Request from SDK to CLI
95    #[serde(rename = "request")]
96    Request(ControlRequest),
97    /// Response from CLI to SDK
98    #[serde(rename = "response")]
99    Response(ControlResponse),
100    /// Initialization request
101    #[serde(rename = "init")]
102    Init(InitRequest),
103    /// Initialization response
104    #[serde(rename = "init_response")]
105    InitResponse(InitResponse),
106}
107
108/// Request from SDK to CLI
109#[derive(Debug, Clone, Serialize, Deserialize)]
110#[serde(tag = "method", content = "params")]
111pub enum ControlRequest {
112    /// Interrupt the current operation
113    #[serde(rename = "interrupt")]
114    Interrupt {
115        /// Unique request identifier
116        id: RequestId,
117    },
118    /// Send a message to Claude
119    #[serde(rename = "send_message")]
120    SendMessage {
121        /// Unique request identifier
122        id: RequestId,
123        /// Message content to send
124        content: String,
125    },
126    /// Respond to a hook invocation
127    #[serde(rename = "hook_response")]
128    HookResponse {
129        /// Unique request identifier
130        id: RequestId,
131        /// Hook event ID being responded to
132        hook_id: String,
133        /// Hook response data
134        response: serde_json::Value,
135    },
136    /// Respond to a permission request
137    #[serde(rename = "permission_response")]
138    PermissionResponse {
139        /// Unique request identifier
140        id: RequestId,
141        /// Permission request ID being responded to
142        request_id: RequestId,
143        /// Permission result (Allow/Deny)
144        result: PermissionResult,
145    },
146}
147
148/// Response from CLI to SDK
149#[derive(Debug, Clone, Serialize, Deserialize)]
150#[serde(tag = "status")]
151pub enum ControlResponse {
152    /// Successful response
153    #[serde(rename = "success")]
154    Success {
155        /// Request ID this responds to
156        id: RequestId,
157        /// Optional response data
158        data: Option<serde_json::Value>,
159    },
160    /// Error response
161    #[serde(rename = "error")]
162    Error {
163        /// Request ID this responds to
164        id: RequestId,
165        /// Error message
166        message: String,
167        /// Error code
168        code: Option<String>,
169    },
170    /// Hook invocation from CLI
171    #[serde(rename = "hook")]
172    Hook {
173        /// Hook invocation ID
174        id: String,
175        /// Hook event details
176        event: HookEvent,
177    },
178    /// Permission request from CLI
179    #[serde(rename = "permission")]
180    Permission {
181        /// Permission request ID
182        id: RequestId,
183        /// Permission request details
184        request: PermissionRequest,
185    },
186}
187
188/// Initialization request sent from SDK to CLI
189#[derive(Debug, Clone, Serialize, Deserialize)]
190pub struct InitRequest {
191    /// Protocol version
192    pub protocol_version: String,
193    /// SDK version
194    pub sdk_version: String,
195    /// Client capabilities
196    pub capabilities: ClientCapabilities,
197}
198
199/// Client capabilities for negotiation
200#[derive(Debug, Clone, Serialize, Deserialize)]
201pub struct ClientCapabilities {
202    /// Supports bidirectional communication
203    pub bidirectional: bool,
204    /// Supports hooks
205    pub hooks: bool,
206    /// Supports permissions
207    pub permissions: bool,
208    /// Supports interrupts
209    pub interrupts: bool,
210}
211
212/// Initialization response from CLI to SDK
213#[derive(Debug, Clone, Serialize, Deserialize)]
214pub struct InitResponse {
215    /// Protocol version accepted
216    pub protocol_version: String,
217    /// CLI version
218    pub cli_version: String,
219    /// Server capabilities
220    pub capabilities: ServerCapabilities,
221    /// Session ID for this connection
222    pub session_id: String,
223}
224
225/// Server capabilities advertised by CLI
226#[derive(Debug, Clone, Serialize, Deserialize)]
227pub struct ServerCapabilities {
228    /// Supports streaming responses
229    pub streaming: bool,
230    /// Supports tool use
231    pub tools: bool,
232    /// Supports MCP servers
233    pub mcp: bool,
234}
235
236/// Pending request awaiting response
237struct PendingRequest {
238    /// Response channel
239    response_tx: oneshot::Sender<ControlResponse>,
240}
241
242/// Protocol handler for managing control protocol communication
243pub struct ProtocolHandler {
244    /// Request ID counter
245    next_request_id: Arc<AtomicU64>,
246    /// Pending requests awaiting responses
247    pending_requests: Arc<Mutex<HashMap<RequestId, PendingRequest>>>,
248    /// Initialized flag
249    initialized: Arc<AtomicBool>,
250    /// Hook callback channel
251    hook_tx: Option<mpsc::UnboundedSender<(String, HookEvent)>>,
252    /// Permission callback channel
253    permission_tx: Option<mpsc::UnboundedSender<(RequestId, PermissionRequest)>>,
254}
255
256impl ProtocolHandler {
257    /// Create a new protocol handler
258    pub fn new() -> Self {
259        Self {
260            next_request_id: Arc::new(AtomicU64::new(1)),
261            pending_requests: Arc::new(Mutex::new(HashMap::new())),
262            initialized: Arc::new(AtomicBool::new(false)),
263            hook_tx: None,
264            permission_tx: None,
265        }
266    }
267
268    /// Set hook callback channel
269    pub fn set_hook_channel(&mut self, tx: mpsc::UnboundedSender<(String, HookEvent)>) {
270        self.hook_tx = Some(tx);
271    }
272
273    /// Set permission callback channel
274    pub fn set_permission_channel(
275        &mut self,
276        tx: mpsc::UnboundedSender<(RequestId, PermissionRequest)>,
277    ) {
278        self.permission_tx = Some(tx);
279    }
280
281    /// Check if protocol is initialized
282    pub fn is_initialized(&self) -> bool {
283        self.initialized.load(Ordering::SeqCst)
284    }
285
286    /// Set protocol as initialized (for cases where no handshake is needed)
287    pub fn set_initialized(&self, value: bool) {
288        self.initialized.store(value, Ordering::SeqCst);
289    }
290
291    /// Generate next request ID
292    fn next_id(&self) -> RequestId {
293        let id = self.next_request_id.fetch_add(1, Ordering::SeqCst);
294        RequestId::new(format!("req-{id}"))
295    }
296
297    /// Create initialization request
298    pub fn create_init_request(&self) -> InitRequest {
299        InitRequest {
300            protocol_version: "1.0".to_string(),
301            sdk_version: crate::VERSION.to_string(),
302            capabilities: ClientCapabilities {
303                bidirectional: true,
304                hooks: true,
305                permissions: true,
306                interrupts: true,
307            },
308        }
309    }
310
311    /// Handle initialization response
312    pub fn handle_init_response(&self, response: InitResponse) -> Result<()> {
313        // Validate protocol version
314        if response.protocol_version != "1.0" {
315            return Err(ClaudeError::protocol_error(format!(
316                "Unsupported protocol version: {}",
317                response.protocol_version
318            )));
319        }
320
321        self.initialized.store(true, Ordering::SeqCst);
322        Ok(())
323    }
324
325    /// Send a request and wait for response
326    pub async fn send_request(
327        &self,
328        request: ControlRequest,
329    ) -> Result<oneshot::Receiver<ControlResponse>> {
330        if !self.is_initialized() {
331            return Err(ClaudeError::protocol_error(
332                "Protocol not initialized - call init first",
333            ));
334        }
335
336        let id = self.get_request_id(&request);
337        let (response_tx, response_rx) = oneshot::channel();
338
339        let pending = PendingRequest { response_tx };
340
341        {
342            let mut pending_requests = self.pending_requests.lock().await;
343            pending_requests.insert(id, pending);
344        }
345
346        Ok(response_rx)
347    }
348
349    /// Extract request ID from a control request
350    fn get_request_id(&self, request: &ControlRequest) -> RequestId {
351        match request {
352            ControlRequest::Interrupt { id } => id.clone(),
353            ControlRequest::SendMessage { id, .. } => id.clone(),
354            ControlRequest::HookResponse { id, .. } => id.clone(),
355            ControlRequest::PermissionResponse { id, .. } => id.clone(),
356        }
357    }
358
359    /// Handle incoming control response
360    pub async fn handle_response(&self, response: ControlResponse) -> Result<()> {
361        match &response {
362            ControlResponse::Success { id, .. } | ControlResponse::Error { id, .. } => {
363                let mut pending_requests = self.pending_requests.lock().await;
364                if let Some(pending) = pending_requests.remove(id) {
365                    let _ = pending.response_tx.send(response);
366                }
367                Ok(())
368            }
369            ControlResponse::Hook { id, event } => {
370                if let Some(ref tx) = self.hook_tx {
371                    tx.send((id.clone(), *event))
372                        .map_err(|_| ClaudeError::protocol_error("Hook channel closed"))?;
373                }
374                Ok(())
375            }
376            ControlResponse::Permission { id, request } => {
377                if let Some(ref tx) = self.permission_tx {
378                    tx.send((id.clone(), request.clone()))
379                        .map_err(|_| ClaudeError::protocol_error("Permission channel closed"))?;
380                }
381                Ok(())
382            }
383        }
384    }
385
386    /// Create interrupt request
387    pub fn create_interrupt_request(&self) -> ControlRequest {
388        ControlRequest::Interrupt {
389            id: self.next_id(),
390        }
391    }
392
393    /// Create send message request
394    pub fn create_send_message_request(&self, content: String) -> ControlRequest {
395        ControlRequest::SendMessage {
396            id: self.next_id(),
397            content,
398        }
399    }
400
401    /// Create hook response
402    pub fn create_hook_response(
403        &self,
404        hook_id: String,
405        response: serde_json::Value,
406    ) -> ControlRequest {
407        ControlRequest::HookResponse {
408            id: self.next_id(),
409            hook_id,
410            response,
411        }
412    }
413
414    /// Create permission response
415    pub fn create_permission_response(
416        &self,
417        request_id: RequestId,
418        result: PermissionResult,
419    ) -> ControlRequest {
420        ControlRequest::PermissionResponse {
421            id: self.next_id(),
422            request_id,
423            result,
424        }
425    }
426
427    /// Serialize control message to JSON
428    pub fn serialize_message(&self, message: &ControlMessage) -> Result<String> {
429        serde_json::to_string(message)
430            .map(|s| format!("{s}\n"))
431            .map_err(|e| ClaudeError::json_encode(format!("Failed to serialize message: {e}")))
432    }
433
434    /// Deserialize control message from JSON
435    pub fn deserialize_message(&self, json: &str) -> Result<ControlMessage> {
436        serde_json::from_str(json)
437            .map_err(|e| ClaudeError::json_decode(format!("Failed to deserialize message: {e}")))
438    }
439}
440
441impl Default for ProtocolHandler {
442    fn default() -> Self {
443        Self::new()
444    }
445}
446
447#[cfg(test)]
448mod tests {
449    use super::*;
450    use crate::types::ToolName;
451
452    #[test]
453    fn test_request_id_generation() {
454        let handler = ProtocolHandler::new();
455        let id1 = handler.next_id();
456        let id2 = handler.next_id();
457        assert_ne!(id1, id2);
458    }
459
460    #[test]
461    fn test_init_request_creation() {
462        let handler = ProtocolHandler::new();
463        let init_req = handler.create_init_request();
464        assert_eq!(init_req.protocol_version, "1.0");
465        assert!(init_req.capabilities.bidirectional);
466    }
467
468    #[test]
469    fn test_serialize_deserialize() {
470        let handler = ProtocolHandler::new();
471        let request = handler.create_interrupt_request();
472        let message = ControlMessage::Request(request);
473
474        let serialized = handler.serialize_message(&message).unwrap();
475        let deserialized = handler.deserialize_message(serialized.trim()).unwrap();
476
477        match deserialized {
478            ControlMessage::Request(ControlRequest::Interrupt { .. }) => {}
479            _ => panic!("Wrong message type"),
480        }
481    }
482
483    #[test]
484    fn test_deserialize_invalid_json() {
485        let handler = ProtocolHandler::new();
486        let result = handler.deserialize_message("not valid json");
487        assert!(result.is_err());
488    }
489
490    #[test]
491    fn test_deserialize_invalid_message_structure() {
492        let handler = ProtocolHandler::new();
493        let invalid = r#"{"type":"unknown_type"}"#;
494        let result = handler.deserialize_message(invalid);
495        assert!(result.is_err());
496    }
497
498    #[test]
499    fn test_deserialize_missing_fields() {
500        let handler = ProtocolHandler::new();
501        let missing = r#"{"type":"request"}"#;
502        let result = handler.deserialize_message(missing);
503        assert!(result.is_err());
504    }
505
506    #[tokio::test]
507    async fn test_handle_response_with_missing_pending_request() {
508        let handler = ProtocolHandler::new();
509        handler.set_initialized(true);
510
511        // Create a response for a request that was never sent
512        let response = ControlResponse::Success {
513            id: RequestId::new("non-existent-req"),
514            data: None,
515        };
516
517        // Should not error, just ignore
518        let result = handler.handle_response(response).await;
519        assert!(result.is_ok());
520    }
521
522    #[tokio::test]
523    async fn test_hook_response_without_channel() {
524        let handler = ProtocolHandler::new();
525
526        // Try to handle hook response without setting up channel
527        let response = ControlResponse::Hook {
528            id: "hook-1".to_string(),
529            event: HookEvent::PreToolUse,
530        };
531
532        // Should not error, just no-op
533        let result = handler.handle_response(response).await;
534        assert!(result.is_ok());
535    }
536
537    #[tokio::test]
538    async fn test_permission_response_without_channel() {
539        let handler = ProtocolHandler::new();
540
541        // Try to handle permission response without setting up channel
542        let response = ControlResponse::Permission {
543            id: RequestId::new("perm-1"),
544            request: PermissionRequest {
545                tool_name: ToolName::new("test"),
546                tool_input: serde_json::json!({}),
547                context: crate::types::ToolPermissionContext {
548                    suggestions: vec![],
549                },
550            },
551        };
552
553        // Should not error, just no-op
554        let result = handler.handle_response(response).await;
555        assert!(result.is_ok());
556    }
557
558    #[test]
559    fn test_init_response_with_wrong_version() {
560        let handler = ProtocolHandler::new();
561
562        let init_response = InitResponse {
563            protocol_version: "999.0".to_string(),
564            cli_version: "1.0.0".to_string(),
565            capabilities: ServerCapabilities {
566                streaming: true,
567                tools: true,
568                mcp: true,
569            },
570            session_id: "test".to_string(),
571        };
572
573        let result = handler.handle_init_response(init_response);
574        assert!(result.is_err());
575        assert!(!handler.is_initialized());
576    }
577
578    #[tokio::test]
579    async fn test_send_request_without_init() {
580        let handler = ProtocolHandler::new();
581        assert!(!handler.is_initialized());
582
583        let request = handler.create_interrupt_request();
584        let result = handler.send_request(request).await;
585        assert!(result.is_err());
586    }
587
588    #[test]
589    fn test_serialize_all_request_types() {
590        let handler = ProtocolHandler::new();
591
592        // Test Interrupt
593        let req = handler.create_interrupt_request();
594        let msg = ControlMessage::Request(req);
595        assert!(handler.serialize_message(&msg).is_ok());
596
597        // Test SendMessage
598        let req = handler.create_send_message_request("test".to_string());
599        let msg = ControlMessage::Request(req);
600        assert!(handler.serialize_message(&msg).is_ok());
601
602        // Test HookResponse
603        let req = handler.create_hook_response("hook-1".to_string(), serde_json::json!({}));
604        let msg = ControlMessage::Request(req);
605        assert!(handler.serialize_message(&msg).is_ok());
606
607        // Test PermissionResponse
608        let req = handler.create_permission_response(
609            RequestId::new("req-1"),
610            crate::types::PermissionResult::Allow(crate::types::PermissionResultAllow {
611                updated_input: None,
612                updated_permissions: None,
613            }),
614        );
615        let msg = ControlMessage::Request(req);
616        assert!(handler.serialize_message(&msg).is_ok());
617    }
618
619    #[test]
620    fn test_serialize_all_response_types() {
621        let handler = ProtocolHandler::new();
622
623        // Test Success
624        let resp = ControlResponse::Success {
625            id: RequestId::new("req-1"),
626            data: Some(serde_json::json!({"result": "ok"})),
627        };
628        let msg = ControlMessage::Response(resp);
629        assert!(handler.serialize_message(&msg).is_ok());
630
631        // Test Error
632        let resp = ControlResponse::Error {
633            id: RequestId::new("req-1"),
634            message: "test error".to_string(),
635            code: Some("ERR_TEST".to_string()),
636        };
637        let msg = ControlMessage::Response(resp);
638        assert!(handler.serialize_message(&msg).is_ok());
639
640        // Test Hook
641        let resp = ControlResponse::Hook {
642            id: "hook-1".to_string(),
643            event: HookEvent::PreToolUse,
644        };
645        let msg = ControlMessage::Response(resp);
646        assert!(handler.serialize_message(&msg).is_ok());
647
648        // Test Permission
649        let resp = ControlResponse::Permission {
650            id: RequestId::new("perm-1"),
651            request: PermissionRequest {
652                tool_name: ToolName::new("test"),
653                tool_input: serde_json::json!({}),
654                context: crate::types::ToolPermissionContext {
655                    suggestions: vec![],
656                },
657            },
658        };
659        let msg = ControlMessage::Response(resp);
660        assert!(handler.serialize_message(&msg).is_ok());
661    }
662
663    #[test]
664    fn test_get_request_id() {
665        let handler = ProtocolHandler::new();
666
667        let interrupt = ControlRequest::Interrupt {
668            id: RequestId::new("id1"),
669        };
670        assert_eq!(handler.get_request_id(&interrupt).as_str(), "id1");
671
672        let send_msg = ControlRequest::SendMessage {
673            id: RequestId::new("id2"),
674            content: "test".to_string(),
675        };
676        assert_eq!(handler.get_request_id(&send_msg).as_str(), "id2");
677
678        let hook_resp = ControlRequest::HookResponse {
679            id: RequestId::new("id3"),
680            hook_id: "hook".to_string(),
681            response: serde_json::json!({}),
682        };
683        assert_eq!(handler.get_request_id(&hook_resp).as_str(), "id3");
684
685        let perm_resp = ControlRequest::PermissionResponse {
686            id: RequestId::new("id4"),
687            request_id: RequestId::new("perm"),
688            result: crate::types::PermissionResult::Allow(
689                crate::types::PermissionResultAllow {
690                    updated_input: None,
691                    updated_permissions: None,
692                },
693            ),
694        };
695        assert_eq!(handler.get_request_id(&perm_resp).as_str(), "id4");
696    }
697}