use crate::error::{Result, SdkError};
use crate::session::SessionOptions;
use crate::streaming::{AssembledEvent, StreamAssembler};
use crate::tools::{ToolResult, ToolUse};
use crate::transport::Transport;
use crate::types::{InputMessage, Message};
#[derive(Debug, Clone)]
pub struct ClaudeClient {
pub cli_path: String,
}
impl Default for ClaudeClient {
fn default() -> Self {
Self::new()
}
}
impl ClaudeClient {
pub fn new() -> Self {
Self {
cli_path: "claude".to_owned(),
}
}
pub fn with_cli_path(cli_path: impl Into<String>) -> Self {
Self {
cli_path: cli_path.into(),
}
}
pub async fn spawn(&self, opts: SessionOptions) -> Result<Session> {
let args = opts.to_cli_args();
let transport = Transport::spawn(
&self.cli_path,
&args,
opts.working_dir.as_deref(),
&opts.env_vars,
)?;
Ok(Session {
transport,
assembler: StreamAssembler::new(),
finished: false,
})
}
}
pub struct Session {
transport: Transport,
assembler: StreamAssembler,
finished: bool,
}
impl Session {
pub async fn send_message(&mut self, text: &str) -> Result<()> {
if self.finished {
return Err(SdkError::NotConnected);
}
let msg = InputMessage::user_text(text);
self.transport.send(&msg).await
}
pub async fn send_message_with_images(
&mut self,
text: &str,
images: Vec<(String, String)>,
) -> Result<()> {
if self.finished {
return Err(SdkError::NotConnected);
}
let msg = InputMessage::user_with_images(text, images);
self.transport.send(&msg).await
}
pub async fn send_tool_result(&mut self, result: &ToolResult) -> Result<()> {
if self.finished {
return Err(SdkError::NotConnected);
}
let msg = InputMessage::tool_result(&result.tool_use_id, &result.output, result.is_error);
self.transport.send(&msg).await
}
pub async fn next_event(&mut self) -> Result<Option<Event>> {
if self.finished {
return Ok(None);
}
loop {
let value = self.transport.recv().await?;
let Some(value) = value else {
self.finished = true;
return Ok(None);
};
let message: Message = match serde_json::from_value(value.clone()) {
Ok(m) => m,
Err(e) => {
tracing::warn!(
error = %e,
line = %value,
"skipping unrecognized message from claude stdout"
);
continue;
}
};
match message {
Message::System(sys) => {
return Ok(Some(Event::System(sys)));
}
Message::User(user) => {
return Ok(Some(Event::User(user)));
}
Message::Assistant(assistant) => {
let tool_uses = ToolUse::extract_from_content(&assistant.message.content);
return Ok(Some(Event::Assistant {
message: assistant,
tool_uses,
}));
}
Message::RateLimitEvent(event) => {
return Ok(Some(Event::RateLimit(event)));
}
Message::Result(result) => {
self.finished = true;
return Ok(Some(Event::Result(result)));
}
Message::StreamEvent(stream_event) => {
let assembled = self.assembler.process(&stream_event.event);
return Ok(Some(Event::Stream {
raw: stream_event,
assembled,
}));
}
}
}
}
pub async fn interrupt(&mut self) -> Result<()> {
self.transport.interrupt()
}
pub fn close_stdin(&mut self) {
self.transport.close_stdin();
}
pub async fn close(mut self) -> Result<(Option<i32>, Option<String>)> {
self.transport.close_stdin();
self.transport.wait_with_stderr().await
}
pub async fn wait_for_stderr(&mut self) -> Result<Option<String>> {
let (_, stderr) = self.transport.wait_with_stderr().await?;
Ok(stderr)
}
pub async fn kill(mut self) -> Result<()> {
self.transport.kill().await
}
pub fn is_finished(&self) -> bool {
self.finished
}
}
#[derive(Debug, Clone)]
pub enum Event {
System(crate::types::SystemMessage),
User(crate::types::UserMessage),
Assistant {
message: crate::types::AssistantMessage,
tool_uses: Vec<ToolUse>,
},
Result(crate::types::ResultMessage),
Stream {
raw: crate::types::StreamEvent,
assembled: Vec<AssembledEvent>,
},
RateLimit(crate::types::RateLimitEvent),
}
impl Event {
pub fn is_result(&self) -> bool {
matches!(self, Event::Result(_))
}
pub fn is_assistant(&self) -> bool {
matches!(self, Event::Assistant { .. })
}
pub fn tool_uses(&self) -> Option<&[ToolUse]> {
match self {
Event::Assistant { tool_uses, .. } if !tool_uses.is_empty() => Some(tool_uses),
_ => None,
}
}
pub fn as_result(&self) -> Option<&crate::types::ResultMessage> {
match self {
Event::Result(r) => Some(r),
_ => None,
}
}
}