1use crate::cli::ClaudeCliBuilder;
4use crate::error::{Error, Result};
5use crate::io::{
6 ClaudeInput, ClaudeOutput, ContentBlock, ControlRequestMessage, ControlResponse,
7 ControlResponseMessage,
8};
9use crate::protocol::Protocol;
10use log::{debug, error, info, warn};
11use serde::{Deserialize, Serialize};
12use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader, BufReader as AsyncBufReader};
13use tokio::process::{Child, ChildStderr, ChildStdin, ChildStdout};
14use uuid::Uuid;
15
16pub struct AsyncClient {
18 child: Child,
19 stdin: ChildStdin,
20 stdout: BufReader<ChildStdout>,
21 stderr: Option<BufReader<ChildStderr>>,
22 session_uuid: Option<Uuid>,
23 tool_approval_enabled: bool,
25}
26
27const STDOUT_BUFFER_SIZE: usize = 10 * 1024 * 1024;
29
30impl AsyncClient {
31 pub fn new(mut child: Child) -> Result<Self> {
33 let stdin = child
34 .stdin
35 .take()
36 .ok_or_else(|| Error::Io(std::io::Error::other("Failed to get stdin handle")))?;
37
38 let stdout = BufReader::with_capacity(
39 STDOUT_BUFFER_SIZE,
40 child
41 .stdout
42 .take()
43 .ok_or_else(|| Error::Io(std::io::Error::other("Failed to get stdout handle")))?,
44 );
45
46 let stderr = child.stderr.take().map(BufReader::new);
47
48 Ok(Self {
49 child,
50 stdin,
51 stdout,
52 stderr,
53 session_uuid: None,
54 tool_approval_enabled: false,
55 })
56 }
57
58 pub async fn with_defaults() -> Result<Self> {
60 crate::version::check_claude_version_async().await?;
66 Self::with_model("sonnet").await
67 }
68
69 pub async fn with_model(model: &str) -> Result<Self> {
71 let child = ClaudeCliBuilder::new().model(model).spawn().await?;
72
73 info!("Started Claude process with model: {}", model);
74 Self::new(child)
75 }
76
77 pub async fn from_builder(builder: ClaudeCliBuilder) -> Result<Self> {
79 let child = builder.spawn().await?;
80 info!("Started Claude process from custom builder");
81 Self::new(child)
82 }
83
84 pub async fn resume_session(session_uuid: Uuid) -> Result<Self> {
87 let child = ClaudeCliBuilder::new()
88 .resume(Some(session_uuid.to_string()))
89 .spawn()
90 .await?;
91
92 info!("Resuming Claude session with UUID: {}", session_uuid);
93 let mut client = Self::new(child)?;
94 client.session_uuid = Some(session_uuid);
96 Ok(client)
97 }
98
99 pub async fn resume_session_with_model(session_uuid: Uuid, model: &str) -> Result<Self> {
101 let child = ClaudeCliBuilder::new()
102 .model(model)
103 .resume(Some(session_uuid.to_string()))
104 .spawn()
105 .await?;
106
107 info!(
108 "Resuming Claude session with UUID: {} and model: {}",
109 session_uuid, model
110 );
111 let mut client = Self::new(child)?;
112 client.session_uuid = Some(session_uuid);
114 Ok(client)
115 }
116
117 pub async fn query(&mut self, text: &str) -> Result<Vec<ClaudeOutput>> {
120 let session_id = Uuid::new_v4();
121 self.query_with_session(text, session_id).await
122 }
123
124 pub async fn query_with_session(
126 &mut self,
127 text: &str,
128 session_id: Uuid,
129 ) -> Result<Vec<ClaudeOutput>> {
130 let input = ClaudeInput::user_message(text, session_id);
132 self.send(&input).await?;
133
134 let mut responses = Vec::new();
136
137 loop {
138 let output = self.receive().await?;
139 let is_result = matches!(&output, ClaudeOutput::Result(_));
140 responses.push(output);
141
142 if is_result {
143 break;
144 }
145 }
146
147 Ok(responses)
148 }
149
150 pub async fn query_stream(&mut self, text: &str) -> Result<ResponseStream<'_>> {
153 let session_id = Uuid::new_v4();
154 self.query_stream_with_session(text, session_id).await
155 }
156
157 pub async fn query_stream_with_session(
159 &mut self,
160 text: &str,
161 session_id: Uuid,
162 ) -> Result<ResponseStream<'_>> {
163 let input = ClaudeInput::user_message(text, session_id);
165 self.send(&input).await?;
166
167 Ok(ResponseStream {
169 client: self,
170 finished: false,
171 })
172 }
173
174 pub async fn send(&mut self, input: &ClaudeInput) -> Result<()> {
176 let json_line = Protocol::serialize(input)?;
177 debug!("[OUTGOING] Sending JSON to Claude: {}", json_line.trim());
178
179 self.stdin
180 .write_all(json_line.as_bytes())
181 .await
182 .map_err(Error::Io)?;
183
184 self.stdin.flush().await.map_err(Error::Io)?;
185 Ok(())
186 }
187
188 pub async fn interrupt(&mut self) -> Result<String> {
196 let request_id = format!("interrupt-{}", Uuid::new_v4());
197 self.send(&ClaudeInput::interrupt(&request_id)).await?;
198 Ok(request_id)
199 }
200
201 pub async fn apply_flag_settings(
209 &mut self,
210 settings: crate::io::ApplyFlagSettingsRequest,
211 ) -> Result<String> {
212 let request_id = format!("apply-flag-settings-{}", Uuid::new_v4());
213 self.send(&ClaudeInput::apply_flag_settings(&request_id, settings))
214 .await?;
215 Ok(request_id)
216 }
217
218 pub async fn set_effort(&mut self, level: crate::EffortLevel) -> Result<String> {
222 self.apply_flag_settings(crate::io::ApplyFlagSettingsRequest::new().effort_level(level))
223 .await
224 }
225
226 pub async fn receive(&mut self) -> Result<ClaudeOutput> {
244 let trimmed = self.read_frame_line().await?;
245 debug!("[INCOMING] Received JSON from Claude: {}", trimmed);
246
247 match ClaudeOutput::parse_json_tolerant(&trimmed) {
249 Ok(output) => {
250 debug!("[INCOMING] Parsed output type: {}", output.message_type());
251
252 if self.session_uuid.is_none() {
254 if let ClaudeOutput::Assistant(ref msg) = output {
255 if let Some(ref uuid_str) = msg.uuid {
256 if let Ok(uuid) = Uuid::parse_str(uuid_str) {
257 debug!("[INCOMING] Captured session UUID: {}", uuid);
258 self.session_uuid = Some(uuid);
259 }
260 }
261 } else if let ClaudeOutput::Result(ref msg) = output {
262 if let Some(ref uuid_str) = msg.uuid {
263 if let Ok(uuid) = Uuid::parse_str(uuid_str) {
264 debug!("[INCOMING] Captured session UUID: {}", uuid);
265 self.session_uuid = Some(uuid);
266 }
267 }
268 }
269 }
270
271 Ok(output)
272 }
273 Err(parse_error) => {
274 warn!("[INCOMING] Failed to deserialize message from Claude CLI. Please report this at https://github.com/meawoppl/rust-claude-codes/issues with the raw message below.");
275 warn!("[INCOMING] Parse error: {}", parse_error.error_message);
276 warn!("[INCOMING] Raw message: {}", trimmed);
277 Err(parse_error.into())
278 }
279 }
280 }
281
282 async fn read_frame_line(&mut self) -> Result<String> {
290 let mut line = String::new();
291 loop {
292 line.clear();
293 let bytes_read = self.stdout.read_line(&mut line).await.map_err(Error::Io)?;
294 if bytes_read == 0 {
295 return Err(Error::ConnectionClosed);
296 }
297 let trimmed = line.trim();
298 if trimmed.is_empty() {
299 continue;
300 }
301 return Ok(trimmed.to_string());
302 }
303 }
304
305 pub async fn receive_raw(&mut self) -> Result<serde_json::Value> {
313 let trimmed = self.read_frame_line().await?;
314 match serde_json::from_str::<serde_json::Value>(&trimmed) {
315 Ok(value) => Ok(value),
316 Err(e) => match trimmed.find('{') {
317 Some(start) => Ok(serde_json::from_str::<serde_json::Value>(&trimmed[start..])
318 .map_err(|_| Error::Json(e))?),
319 None => Err(Error::Json(e)),
320 },
321 }
322 }
323
324 pub fn is_alive(&mut self) -> bool {
326 self.child.try_wait().ok().flatten().is_none()
327 }
328
329 pub async fn shutdown(mut self) -> Result<()> {
331 info!("Shutting down Claude process...");
332 self.child.kill().await.map_err(Error::Io)?;
333 Ok(())
334 }
335
336 pub fn pid(&self) -> Option<u32> {
338 self.child.id()
339 }
340
341 pub fn take_stderr(&mut self) -> Option<BufReader<ChildStderr>> {
343 self.stderr.take()
344 }
345
346 pub fn session_uuid(&self) -> Result<Uuid> {
349 self.session_uuid.ok_or(Error::SessionNotInitialized)
350 }
351
352 pub async fn ping(&mut self) -> bool {
355 let ping_input = ClaudeInput::user_message(
357 "ping - respond with just the word 'pong' and nothing else",
358 self.session_uuid.unwrap_or_else(Uuid::new_v4),
359 );
360
361 if let Err(e) = self.send(&ping_input).await {
363 debug!("Ping failed to send: {}", e);
364 return false;
365 }
366
367 let mut found_pong = false;
369 let mut message_count = 0;
370 const MAX_MESSAGES: usize = 10;
371
372 loop {
373 match self.receive().await {
374 Ok(output) => {
375 message_count += 1;
376
377 if let ClaudeOutput::Assistant(msg) = &output {
379 for content in &msg.message.content {
380 if let ContentBlock::Text(text) = content {
381 if text.text.to_lowercase().contains("pong") {
382 found_pong = true;
383 }
384 }
385 }
386 }
387
388 if matches!(output, ClaudeOutput::Result(_)) {
390 break;
391 }
392
393 if message_count >= MAX_MESSAGES {
395 debug!("Ping exceeded message limit");
396 break;
397 }
398 }
399 Err(e) => {
400 debug!("Ping failed to receive response: {}", e);
401 break;
402 }
403 }
404 }
405
406 found_pong
407 }
408
409 pub async fn enable_tool_approval(&mut self) -> Result<()> {
443 if self.tool_approval_enabled {
444 debug!("[TOOL_APPROVAL] Already enabled, skipping initialization");
445 return Ok(());
446 }
447
448 let request_id = format!("init-{}", Uuid::new_v4());
449 let init_request = ControlRequestMessage::initialize(&request_id);
450
451 debug!("[TOOL_APPROVAL] Sending initialization handshake");
452 let json_line = Protocol::serialize(&init_request)?;
453 self.stdin
454 .write_all(json_line.as_bytes())
455 .await
456 .map_err(Error::Io)?;
457 self.stdin.flush().await.map_err(Error::Io)?;
458
459 loop {
461 let mut line = String::new();
462 let bytes_read = self.stdout.read_line(&mut line).await.map_err(Error::Io)?;
463
464 if bytes_read == 0 {
465 return Err(Error::ConnectionClosed);
466 }
467
468 let trimmed = line.trim();
469 if trimmed.is_empty() {
470 continue;
471 }
472
473 debug!("[TOOL_APPROVAL] Received: {}", trimmed);
474
475 match ClaudeOutput::parse_json_tolerant(trimmed) {
477 Ok(ClaudeOutput::ControlResponse(resp)) => {
478 use crate::io::ControlResponsePayload;
479 match &resp.response {
480 ControlResponsePayload::Success {
481 request_id: rid, ..
482 } if rid == &request_id => {
483 debug!("[TOOL_APPROVAL] Initialization successful");
484 self.tool_approval_enabled = true;
485 return Ok(());
486 }
487 ControlResponsePayload::Error { error, .. } => {
488 return Err(Error::Protocol(format!(
489 "Tool approval initialization failed: {}",
490 error
491 )));
492 }
493 _ => {
494 continue;
496 }
497 }
498 }
499 Ok(_) => {
500 continue;
502 }
503 Err(e) => {
504 return Err(e.into());
505 }
506 }
507 }
508 }
509
510 pub async fn send_control_response(&mut self, response: ControlResponse) -> Result<()> {
538 let message: ControlResponseMessage = response.into();
539 let json_line = Protocol::serialize(&message)?;
540 debug!(
541 "[TOOL_APPROVAL] Sending control response: {}",
542 json_line.trim()
543 );
544
545 self.stdin
546 .write_all(json_line.as_bytes())
547 .await
548 .map_err(Error::Io)?;
549 self.stdin.flush().await.map_err(Error::Io)?;
550 Ok(())
551 }
552
553 pub fn is_tool_approval_enabled(&self) -> bool {
555 self.tool_approval_enabled
556 }
557}
558
559pub struct ResponseStream<'a> {
562 client: &'a mut AsyncClient,
563 finished: bool,
564}
565
566impl ResponseStream<'_> {
567 pub async fn collect(mut self) -> Result<Vec<ClaudeOutput>> {
569 let mut responses = Vec::new();
570
571 while !self.finished {
572 let output = self.client.receive().await?;
573 let is_result = matches!(&output, ClaudeOutput::Result(_));
574 responses.push(output);
575
576 if is_result {
577 self.finished = true;
578 break;
579 }
580 }
581
582 Ok(responses)
583 }
584
585 pub async fn next(&mut self) -> Option<Result<ClaudeOutput>> {
587 if self.finished {
588 return None;
589 }
590
591 match self.client.receive().await {
592 Ok(output) => {
593 if matches!(&output, ClaudeOutput::Result(_)) {
594 self.finished = true;
595 }
596 Some(Ok(output))
597 }
598 Err(e) => {
599 self.finished = true;
600 Some(Err(e))
601 }
602 }
603 }
604}
605
606impl Drop for AsyncClient {
607 fn drop(&mut self) {
608 if self.is_alive() {
609 if let Err(e) = self.child.start_kill() {
611 error!("Failed to kill Claude process on drop: {}", e);
612 }
613 }
614 }
615}
616
617impl Protocol {
619 pub async fn write_async<W: AsyncWriteExt + Unpin, T: Serialize>(
621 writer: &mut W,
622 message: &T,
623 ) -> Result<()> {
624 let line = Self::serialize(message)?;
625 debug!("[PROTOCOL] Sending async: {}", line.trim());
626 writer.write_all(line.as_bytes()).await?;
627 writer.flush().await?;
628 Ok(())
629 }
630
631 pub async fn read_async<R: AsyncBufReadExt + Unpin, T: for<'de> Deserialize<'de>>(
633 reader: &mut R,
634 ) -> Result<T> {
635 let mut line = String::new();
636 let bytes_read = reader.read_line(&mut line).await?;
637 if bytes_read == 0 {
638 return Err(Error::ConnectionClosed);
639 }
640 debug!("[PROTOCOL] Received async: {}", line.trim());
641 Self::deserialize(&line)
642 }
643}
644
645pub struct AsyncStreamProcessor<R> {
647 reader: AsyncBufReader<R>,
648}
649
650impl<R: tokio::io::AsyncRead + Unpin> AsyncStreamProcessor<R> {
651 pub fn new(reader: R) -> Self {
653 Self {
654 reader: AsyncBufReader::new(reader),
655 }
656 }
657
658 pub async fn next_message<T: for<'de> Deserialize<'de>>(&mut self) -> Result<T> {
660 Protocol::read_async(&mut self.reader).await
661 }
662
663 pub async fn process_all<T, F, Fut>(&mut self, mut handler: F) -> Result<()>
665 where
666 T: for<'de> Deserialize<'de>,
667 F: FnMut(T) -> Fut,
668 Fut: std::future::Future<Output = Result<()>>,
669 {
670 loop {
671 match self.next_message().await {
672 Ok(message) => handler(message).await?,
673 Err(Error::ConnectionClosed) => break,
674 Err(e) => return Err(e),
675 }
676 }
677 Ok(())
678 }
679}