claude_codes/client/
sync.rs1use 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;
10use uuid::Uuid;
11
12pub struct SyncClient {
14 child: Child,
15 stdin: ChildStdin,
16 stdout: BufReader<ChildStdout>,
17 session_uuid: Option<Uuid>,
18}
19
20impl SyncClient {
21 pub fn new(mut child: Child) -> Result<Self> {
23 let stdin = child
24 .stdin
25 .take()
26 .ok_or_else(|| Error::Protocol("Failed to get stdin".to_string()))?;
27 let stdout = child
28 .stdout
29 .take()
30 .ok_or_else(|| Error::Protocol("Failed to get stdout".to_string()))?;
31
32 Ok(Self {
33 child,
34 stdin,
35 stdout: BufReader::new(stdout),
36 session_uuid: None,
37 })
38 }
39
40 pub fn with_defaults() -> Result<Self> {
42 crate::version::check_claude_version()?;
48 let child = ClaudeCliBuilder::new().spawn_sync().map_err(Error::Io)?;
49 Self::new(child)
50 }
51
52 pub fn resume_session(session_uuid: Uuid) -> Result<Self> {
55 let child = ClaudeCliBuilder::new()
56 .resume(Some(session_uuid.to_string()))
57 .spawn_sync()
58 .map_err(Error::Io)?;
59
60 debug!("Resuming Claude session with UUID: {}", session_uuid);
61 let mut client = Self::new(child)?;
62 client.session_uuid = Some(session_uuid);
64 Ok(client)
65 }
66
67 pub fn resume_session_with_model(session_uuid: Uuid, model: &str) -> Result<Self> {
69 let child = ClaudeCliBuilder::new()
70 .model(model)
71 .resume(Some(session_uuid.to_string()))
72 .spawn_sync()
73 .map_err(Error::Io)?;
74
75 debug!(
76 "Resuming Claude session with UUID: {} and model: {}",
77 session_uuid, model
78 );
79 let mut client = Self::new(child)?;
80 client.session_uuid = Some(session_uuid);
82 Ok(client)
83 }
84
85 pub fn query(&mut self, input: ClaudeInput) -> Result<Vec<ClaudeOutput>> {
87 let mut responses = Vec::new();
88 for response in self.query_stream(input)? {
89 responses.push(response?);
90 }
91 Ok(responses)
92 }
93
94 pub fn query_stream(&mut self, input: ClaudeInput) -> Result<ResponseIterator<'_>> {
96 Protocol::write_sync(&mut self.stdin, &input)?;
98
99 Ok(ResponseIterator {
100 client: self,
101 finished: false,
102 })
103 }
104
105 fn read_next(&mut self) -> Result<Option<ClaudeOutput>> {
107 let mut line = String::new();
108 match self.stdout.read_line(&mut line) {
109 Ok(0) => {
110 debug!("[CLIENT] Stream closed");
111 Ok(None)
112 }
113 Ok(_) => {
114 let trimmed = line.trim();
115 if trimmed.is_empty() {
116 debug!("[CLIENT] Skipping empty line");
117 return self.read_next();
118 }
119
120 debug!("[CLIENT] Received: {}", trimmed);
121 match ClaudeOutput::parse_json(trimmed) {
122 Ok(output) => {
123 if self.session_uuid.is_none() {
125 if let ClaudeOutput::Assistant(ref msg) = output {
126 if let Some(ref uuid_str) = msg.uuid {
127 if let Ok(uuid) = Uuid::parse_str(uuid_str) {
128 debug!("[CLIENT] Captured session UUID: {}", uuid);
129 self.session_uuid = Some(uuid);
130 }
131 }
132 } else if let ClaudeOutput::Result(ref msg) = output {
133 if let Some(ref uuid_str) = msg.uuid {
134 if let Ok(uuid) = Uuid::parse_str(uuid_str) {
135 debug!("[CLIENT] Captured session UUID: {}", uuid);
136 self.session_uuid = Some(uuid);
137 }
138 }
139 }
140 }
141
142 if matches!(output, ClaudeOutput::Result(_)) {
144 debug!("[CLIENT] Received result message, stream complete");
145 Ok(Some(output))
146 } else {
147 Ok(Some(output))
148 }
149 }
150 Err(ParseError { error_message, .. }) => {
151 debug!("[CLIENT] Failed to deserialize: {}", error_message);
152 Err(Error::Deserialization(error_message))
153 }
154 }
155 }
156 Err(e) => {
157 debug!("[CLIENT] Error reading from stdout: {}", e);
158 Err(Error::Io(e))
159 }
160 }
161 }
162
163 pub fn shutdown(&mut self) -> Result<()> {
165 debug!("[CLIENT] Shutting down client");
166 self.child.kill().map_err(Error::Io)?;
167 self.child.wait().map_err(Error::Io)?;
168 Ok(())
169 }
170
171 pub fn session_uuid(&self) -> Result<Uuid> {
174 self.session_uuid.ok_or(Error::SessionNotInitialized)
175 }
176}
177
178pub struct ResponseIterator<'a> {
180 client: &'a mut SyncClient,
181 finished: bool,
182}
183
184impl Iterator for ResponseIterator<'_> {
185 type Item = Result<ClaudeOutput>;
186
187 fn next(&mut self) -> Option<Self::Item> {
188 if self.finished {
189 return None;
190 }
191
192 match self.client.read_next() {
193 Ok(Some(output)) => {
194 if matches!(output, ClaudeOutput::Result(_)) {
196 self.finished = true;
197 }
198 Some(Ok(output))
199 }
200 Ok(None) => {
201 self.finished = true;
202 None
203 }
204 Err(e) => {
205 self.finished = true;
206 Some(Err(e))
207 }
208 }
209 }
210}
211
212impl Drop for SyncClient {
213 fn drop(&mut self) {
214 if let Err(e) = self.shutdown() {
215 debug!("[CLIENT] Error during shutdown: {}", e);
216 }
217 }
218}