use crate::core::{
validate_query, ClaudeCliResponse, ClaudeResponse, Config, Result, SessionId, StreamFormat,
};
use crate::runtime::{process::execute_claude, stream::MessageStream};
use std::sync::Arc;
fn extract_text_from_message(msg: &serde_json::Value, result: &mut String) {
let Some(message) = msg.get("message") else {
return;
};
let Some(content_array) = message.get("content").and_then(|v| v.as_array()) else {
return;
};
for content_item in content_array {
extract_text_from_content_item(content_item, result);
}
}
fn extract_text_from_content_item(content_item: &serde_json::Value, result: &mut String) {
if content_item.get("type").and_then(|v| v.as_str()) != Some("text") {
return;
}
if let Some(text) = content_item.get("text").and_then(|v| v.as_str()) {
result.push_str(text);
}
}
fn parse_stream_json_output(output: &str) -> Result<ClaudeResponse> {
let mut result = String::new();
let mut all_json = Vec::new();
for line in output.lines() {
process_stream_json_line(line, &mut all_json, &mut result);
}
let raw_json = serde_json::Value::Array(all_json);
Ok(ClaudeResponse::with_json(result, raw_json))
}
fn process_stream_json_line(line: &str, all_json: &mut Vec<serde_json::Value>, result: &mut String) {
if line.trim().is_empty() {
return;
}
let Ok(msg) = serde_json::from_str::<serde_json::Value>(line) else {
return;
};
all_json.push(msg.clone());
if msg.get("type").and_then(|v| v.as_str()) == Some("assistant") {
extract_text_from_message(&msg, result);
}
}
#[derive(Debug, Clone)]
pub struct Client {
config: Arc<Config>,
}
impl Client {
pub fn new(config: Config) -> Self {
Self {
config: Arc::new(config),
}
}
pub fn builder() -> ClientBuilder {
ClientBuilder::new()
}
pub fn query(&self, query: impl Into<String>) -> QueryBuilder {
QueryBuilder::new(self.clone(), query.into())
}
pub async fn send(&self, query: &str) -> Result<String> {
validate_query(query)?;
let response = self.send_full(query).await?;
Ok(response.content)
}
pub async fn send_full(&self, query: &str) -> Result<ClaudeResponse> {
validate_query(query)?;
let output = execute_claude(&self.config, query).await?;
match self.config.stream_format {
StreamFormat::Text => Ok(ClaudeResponse::text(output.trim().to_string())),
StreamFormat::Json => {
let json_value: serde_json::Value = serde_json::from_str(&output)?;
let claude_response: ClaudeCliResponse =
serde_json::from_value(json_value.clone())?;
Ok(ClaudeResponse::with_json(
claude_response.result,
json_value,
))
}
StreamFormat::StreamJson => {
parse_stream_json_output(&output)
}
}
}
}
pub struct ClientBuilder {
config: Config,
}
impl Default for ClientBuilder {
fn default() -> Self {
Self::new()
}
}
impl ClientBuilder {
pub fn new() -> Self {
Self {
config: Config::default(),
}
}
pub fn config(mut self, config: Config) -> Self {
self.config = config;
self
}
pub fn system_prompt(mut self, prompt: impl Into<String>) -> Self {
self.config.system_prompt = Some(prompt.into());
self
}
pub fn model(mut self, model: impl Into<String>) -> Self {
self.config.model = Some(model.into());
self
}
pub fn allowed_tools(mut self, tools: Vec<String>) -> Self {
self.config.allowed_tools = Some(tools);
self
}
pub fn stream_format(mut self, format: StreamFormat) -> Self {
self.config.stream_format = format;
self
}
pub fn verbose(mut self, verbose: bool) -> Self {
self.config.verbose = verbose;
self
}
pub fn timeout_secs(mut self, timeout_secs: u64) -> Self {
self.config.timeout_secs = Some(timeout_secs);
self
}
pub fn continue_session(mut self) -> Self {
self.config.continue_session = true;
self
}
pub fn resume_session(mut self, session_id: impl Into<String>) -> Self {
self.config.resume_session_id = Some(session_id.into());
self
}
pub fn disallowed_tools(mut self, tools: Vec<String>) -> Self {
self.config.disallowed_tools = Some(tools);
self
}
pub fn skip_permissions(mut self, skip: bool) -> Self {
self.config.skip_permissions = skip;
self
}
pub fn append_system_prompt(mut self, prompt: impl Into<String>) -> Self {
self.config.append_system_prompt = Some(prompt.into());
self
}
pub fn max_turns(mut self, turns: u32) -> Self {
self.config.max_turns = Some(turns);
self
}
pub fn build(self) -> Result<Client> {
self.config.validate()?;
Ok(Client::new(self.config))
}
}
#[derive(Debug)]
pub struct QueryBuilder {
client: Client,
query: String,
session_id: Option<SessionId>,
format: Option<StreamFormat>,
}
impl QueryBuilder {
fn new(client: Client, query: String) -> Self {
Self {
client,
query,
session_id: None,
format: None,
}
}
pub fn session(mut self, session_id: SessionId) -> Self {
self.session_id = Some(session_id);
self
}
pub fn format(mut self, format: StreamFormat) -> Self {
self.format = Some(format);
self
}
pub async fn send(self) -> Result<String> {
self.client.send(&self.query).await
}
pub async fn send_full(self) -> Result<ClaudeResponse> {
self.client.send_full(&self.query).await
}
pub async fn stream(self) -> Result<MessageStream> {
use crate::runtime::process::execute_claude_streaming;
let format = self.format.unwrap_or(self.client.config.stream_format);
let line_receiver = execute_claude_streaming(&self.client.config, &self.query).await?;
Ok(MessageStream::from_line_stream(line_receiver, format).await)
}
pub async fn parse_output<T: serde::de::DeserializeOwned>(self) -> Result<T> {
let response = self.send().await?;
serde_json::from_str(&response).map_err(Into::into)
}
}