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 query(&mut self, input: ClaudeInput) -> Result<Vec<ClaudeOutput>> {
54 let mut responses = Vec::new();
55 for response in self.query_stream(input)? {
56 responses.push(response?);
57 }
58 Ok(responses)
59 }
60
61 pub fn query_stream(&mut self, input: ClaudeInput) -> Result<ResponseIterator<'_>> {
63 Protocol::write_sync(&mut self.stdin, &input)?;
65
66 Ok(ResponseIterator {
67 client: self,
68 finished: false,
69 })
70 }
71
72 fn read_next(&mut self) -> Result<Option<ClaudeOutput>> {
74 let mut line = String::new();
75 match self.stdout.read_line(&mut line) {
76 Ok(0) => {
77 debug!("[CLIENT] Stream closed");
78 Ok(None)
79 }
80 Ok(_) => {
81 let trimmed = line.trim();
82 if trimmed.is_empty() {
83 debug!("[CLIENT] Skipping empty line");
84 return self.read_next();
85 }
86
87 debug!("[CLIENT] Received: {}", trimmed);
88 match ClaudeOutput::parse_json(trimmed) {
89 Ok(output) => {
90 if self.session_uuid.is_none() {
92 if let ClaudeOutput::Assistant(ref msg) = output {
93 if let Some(ref uuid_str) = msg.uuid {
94 if let Ok(uuid) = Uuid::parse_str(uuid_str) {
95 debug!("[CLIENT] Captured session UUID: {}", uuid);
96 self.session_uuid = Some(uuid);
97 }
98 }
99 } else if let ClaudeOutput::Result(ref msg) = output {
100 if let Some(ref uuid_str) = msg.uuid {
101 if let Ok(uuid) = Uuid::parse_str(uuid_str) {
102 debug!("[CLIENT] Captured session UUID: {}", uuid);
103 self.session_uuid = Some(uuid);
104 }
105 }
106 }
107 }
108
109 if matches!(output, ClaudeOutput::Result(_)) {
111 debug!("[CLIENT] Received result message, stream complete");
112 Ok(Some(output))
113 } else {
114 Ok(Some(output))
115 }
116 }
117 Err(ParseError { error_message, .. }) => {
118 debug!("[CLIENT] Failed to deserialize: {}", error_message);
119 Err(Error::Deserialization(error_message))
120 }
121 }
122 }
123 Err(e) => {
124 debug!("[CLIENT] Error reading from stdout: {}", e);
125 Err(Error::Io(e))
126 }
127 }
128 }
129
130 pub fn shutdown(&mut self) -> Result<()> {
132 debug!("[CLIENT] Shutting down client");
133 self.child.kill().map_err(Error::Io)?;
134 self.child.wait().map_err(Error::Io)?;
135 Ok(())
136 }
137
138 pub fn session_uuid(&self) -> Result<Uuid> {
141 self.session_uuid.ok_or(Error::SessionNotInitialized)
142 }
143}
144
145pub struct ResponseIterator<'a> {
147 client: &'a mut SyncClient,
148 finished: bool,
149}
150
151impl Iterator for ResponseIterator<'_> {
152 type Item = Result<ClaudeOutput>;
153
154 fn next(&mut self) -> Option<Self::Item> {
155 if self.finished {
156 return None;
157 }
158
159 match self.client.read_next() {
160 Ok(Some(output)) => {
161 if matches!(output, ClaudeOutput::Result(_)) {
163 self.finished = true;
164 }
165 Some(Ok(output))
166 }
167 Ok(None) => {
168 self.finished = true;
169 None
170 }
171 Err(e) => {
172 self.finished = true;
173 Some(Err(e))
174 }
175 }
176 }
177}
178
179impl Drop for SyncClient {
180 fn drop(&mut self) {
181 if let Err(e) = self.shutdown() {
182 debug!("[CLIENT] Error during shutdown: {}", e);
183 }
184 }
185}