claude_codes/client/
sync.rs

1//! Synchronous client for Claude communication
2
3use crate::cli::ClaudeCliBuilder;
4use crate::error::{Error, Result};
5use crate::io::{ClaudeInput, ClaudeOutput, ParseError};
6use crate::protocol::Protocol;
7use std::io::{BufRead, BufReader};
8use std::process::{Child, ChildStdin, ChildStdout};
9use tracing::debug;
10
11/// Synchronous client for communicating with Claude
12pub struct SyncClient {
13    child: Child,
14    stdin: ChildStdin,
15    stdout: BufReader<ChildStdout>,
16}
17
18impl SyncClient {
19    /// Create a new synchronous client from an existing child process
20    pub fn new(mut child: Child) -> Result<Self> {
21        let stdin = child
22            .stdin
23            .take()
24            .ok_or_else(|| Error::Protocol("Failed to get stdin".to_string()))?;
25        let stdout = child
26            .stdout
27            .take()
28            .ok_or_else(|| Error::Protocol("Failed to get stdout".to_string()))?;
29
30        Ok(Self {
31            child,
32            stdin,
33            stdout: BufReader::new(stdout),
34        })
35    }
36
37    /// Create a new synchronous client with default settings
38    pub fn with_defaults() -> Result<Self> {
39        // Check Claude version (only warns once per session)
40        // NOTE: The claude-codes API is in high flux. If you wish to work around
41        // this version check, you can use SyncClient::new() directly with:
42        //   let child = ClaudeCliBuilder::new().spawn_sync()?;
43        //   SyncClient::new(child)
44        crate::version::check_claude_version()?;
45        let child = ClaudeCliBuilder::new().spawn_sync().map_err(Error::Io)?;
46        Self::new(child)
47    }
48
49    /// Send a query and collect all responses
50    pub fn query(&mut self, input: ClaudeInput) -> Result<Vec<ClaudeOutput>> {
51        let mut responses = Vec::new();
52        for response in self.query_stream(input)? {
53            responses.push(response?);
54        }
55        Ok(responses)
56    }
57
58    /// Send a query and return an iterator over responses
59    pub fn query_stream(&mut self, input: ClaudeInput) -> Result<ResponseIterator<'_>> {
60        // Send the input
61        Protocol::write_sync(&mut self.stdin, &input)?;
62
63        Ok(ResponseIterator {
64            client: self,
65            finished: false,
66        })
67    }
68
69    /// Read the next response from Claude
70    fn read_next(&mut self) -> Result<Option<ClaudeOutput>> {
71        let mut line = String::new();
72        match self.stdout.read_line(&mut line) {
73            Ok(0) => {
74                debug!("[CLIENT] Stream closed");
75                Ok(None)
76            }
77            Ok(_) => {
78                let trimmed = line.trim();
79                if trimmed.is_empty() {
80                    debug!("[CLIENT] Skipping empty line");
81                    return self.read_next();
82                }
83
84                debug!("[CLIENT] Received: {}", trimmed);
85                match ClaudeOutput::parse_json(trimmed) {
86                    Ok(output) => {
87                        // Check if this is a result message
88                        if matches!(output, ClaudeOutput::Result(_)) {
89                            debug!("[CLIENT] Received result message, stream complete");
90                            Ok(Some(output))
91                        } else {
92                            Ok(Some(output))
93                        }
94                    }
95                    Err(ParseError { error_message, .. }) => {
96                        debug!("[CLIENT] Failed to deserialize: {}", error_message);
97                        Err(Error::Deserialization(error_message))
98                    }
99                }
100            }
101            Err(e) => {
102                debug!("[CLIENT] Error reading from stdout: {}", e);
103                Err(Error::Io(e))
104            }
105        }
106    }
107
108    /// Shutdown the client and wait for the process to exit
109    pub fn shutdown(&mut self) -> Result<()> {
110        debug!("[CLIENT] Shutting down client");
111        self.child.kill().map_err(Error::Io)?;
112        self.child.wait().map_err(Error::Io)?;
113        Ok(())
114    }
115}
116
117/// Iterator over responses from Claude
118pub struct ResponseIterator<'a> {
119    client: &'a mut SyncClient,
120    finished: bool,
121}
122
123impl Iterator for ResponseIterator<'_> {
124    type Item = Result<ClaudeOutput>;
125
126    fn next(&mut self) -> Option<Self::Item> {
127        if self.finished {
128            return None;
129        }
130
131        match self.client.read_next() {
132            Ok(Some(output)) => {
133                // Check if this is a result message
134                if matches!(output, ClaudeOutput::Result(_)) {
135                    self.finished = true;
136                }
137                Some(Ok(output))
138            }
139            Ok(None) => {
140                self.finished = true;
141                None
142            }
143            Err(e) => {
144                self.finished = true;
145                Some(Err(e))
146            }
147        }
148    }
149}
150
151impl Drop for SyncClient {
152    fn drop(&mut self) {
153        if let Err(e) = self.shutdown() {
154            debug!("[CLIENT] Error during shutdown: {}", e);
155        }
156    }
157}