claude_code_agent_sdk/
query.rs

1//! Simple query function for one-shot interactions
2
3use crate::errors::Result;
4use crate::internal::client::InternalClient;
5use crate::internal::message_parser::MessageParser;
6use crate::internal::transport::subprocess::QueryPrompt;
7use crate::internal::transport::{SubprocessTransport, Transport};
8use crate::types::config::ClaudeAgentOptions;
9use crate::types::messages::{Message, UserContentBlock};
10use futures::stream::{Stream, StreamExt};
11use std::pin::Pin;
12
13/// Query Claude Code for one-shot interactions.
14///
15/// This function is ideal for simple, stateless queries where you don't need
16/// bidirectional communication or conversation management.
17///
18/// # Examples
19///
20/// ```no_run
21/// use claude_agent_sdk_rs::{query, Message, ContentBlock};
22///
23/// #[tokio::main]
24/// async fn main() -> anyhow::Result<()> {
25///     let messages = query("What is 2 + 2?", None).await?;
26///
27///     for message in messages {
28///         match message {
29///             Message::Assistant(msg) => {
30///                 for block in &msg.message.content {
31///                     if let ContentBlock::Text(text) = block {
32///                         println!("Claude: {}", text.text);
33///                     }
34///                 }
35///             }
36///             _ => {}
37///         }
38///     }
39///
40///     Ok(())
41/// }
42/// ```
43pub async fn query(
44    prompt: impl Into<String>,
45    options: Option<ClaudeAgentOptions>,
46) -> Result<Vec<Message>> {
47    let query_prompt = QueryPrompt::Text(prompt.into());
48    let opts = options.unwrap_or_default();
49
50    let client = InternalClient::new(query_prompt, opts)?;
51    client.execute().await
52}
53
54/// Query Claude Code with streaming responses for memory-efficient processing.
55///
56/// Unlike `query()` which collects all messages in memory before returning,
57/// this function returns a stream that yields messages as they arrive from Claude.
58/// This is more memory-efficient for large conversations and provides real-time
59/// message processing capabilities.
60///
61/// # Performance Comparison
62///
63/// - **`query()`**: O(n) memory usage, waits for all messages before returning
64/// - **`query_stream()`**: O(1) memory per message, processes messages in real-time
65///
66/// # Examples
67///
68/// ```no_run
69/// use claude_agent_sdk_rs::{query_stream, Message, ContentBlock};
70/// use futures::stream::StreamExt;
71///
72/// #[tokio::main]
73/// async fn main() -> anyhow::Result<()> {
74///     let mut stream = query_stream("What is 2 + 2?", None).await?;
75///
76///     while let Some(result) = stream.next().await {
77///         match result? {
78///             Message::Assistant(msg) => {
79///                 for block in &msg.message.content {
80///                     if let ContentBlock::Text(text) = block {
81///                         println!("Claude: {}", text.text);
82///                     }
83///                 }
84///             }
85///             _ => {}
86///         }
87///     }
88///
89///     Ok(())
90/// }
91/// ```
92pub async fn query_stream(
93    prompt: impl Into<String>,
94    options: Option<ClaudeAgentOptions>,
95) -> Result<Pin<Box<dyn Stream<Item = Result<Message>> + Send>>> {
96    let query_prompt = QueryPrompt::Text(prompt.into());
97    let opts = options.unwrap_or_default();
98
99    let mut transport = SubprocessTransport::new(query_prompt, opts)?;
100    transport.connect().await?;
101
102    // Move transport into the stream to extend its lifetime
103    let stream = async_stream::stream! {
104        let mut message_stream = transport.read_messages();
105        while let Some(json_result) = message_stream.next().await {
106            match json_result {
107                Ok(json) => {
108                    match MessageParser::parse(json) {
109                        Ok(message) => yield Ok(message),
110                        Err(e) => {
111                            yield Err(e);
112                            break;
113                        }
114                    }
115                }
116                Err(e) => {
117                    yield Err(e);
118                    break;
119                }
120            }
121        }
122    };
123
124    Ok(Box::pin(stream))
125}
126
127/// Query Claude Code with structured content blocks (supports images).
128///
129/// This function allows you to send mixed content including text and images
130/// to Claude. Use [`UserContentBlock`] to construct the content array.
131///
132/// # Errors
133///
134/// Returns an error if:
135/// - The content vector is empty (must include at least one text or image block)
136/// - Claude CLI cannot be found or started
137/// - The query execution fails
138///
139/// # Examples
140///
141/// ```no_run
142/// use claude_agent_sdk_rs::{query_with_content, Message, ContentBlock, UserContentBlock};
143///
144/// #[tokio::main]
145/// async fn main() -> anyhow::Result<()> {
146///     // Create content with text and image
147///     let content = vec![
148///         UserContentBlock::text("What's in this image?"),
149///         UserContentBlock::image_url("https://example.com/image.png"),
150///     ];
151///
152///     let messages = query_with_content(content, None).await?;
153///
154///     for message in messages {
155///         if let Message::Assistant(msg) = message {
156///             for block in &msg.message.content {
157///                 if let ContentBlock::Text(text) = block {
158///                     println!("Claude: {}", text.text);
159///                 }
160///             }
161///         }
162///     }
163///
164///     Ok(())
165/// }
166/// ```
167pub async fn query_with_content(
168    content: impl Into<Vec<UserContentBlock>>,
169    options: Option<ClaudeAgentOptions>,
170) -> Result<Vec<Message>> {
171    let content_blocks = content.into();
172    UserContentBlock::validate_content(&content_blocks)?;
173
174    let query_prompt = QueryPrompt::Content(content_blocks);
175    let opts = options.unwrap_or_default();
176
177    let client = InternalClient::new(query_prompt, opts)?;
178    client.execute().await
179}
180
181/// Query Claude Code with streaming and structured content blocks.
182///
183/// Combines the benefits of [`query_stream`] (memory efficiency, real-time processing)
184/// with support for structured content blocks including images.
185///
186/// # Errors
187///
188/// Returns an error if:
189/// - The content vector is empty (must include at least one text or image block)
190/// - Claude CLI cannot be found or started
191/// - The streaming connection fails
192///
193/// # Examples
194///
195/// ```no_run
196/// use claude_agent_sdk_rs::{query_stream_with_content, Message, ContentBlock, UserContentBlock};
197/// use futures::stream::StreamExt;
198///
199/// #[tokio::main]
200/// async fn main() -> anyhow::Result<()> {
201///     // Create content with base64 image
202///     let content = vec![
203///         UserContentBlock::image_base64("image/png", "iVBORw0KGgo...")?,
204///         UserContentBlock::text("Describe this diagram in detail"),
205///     ];
206///
207///     let mut stream = query_stream_with_content(content, None).await?;
208///
209///     while let Some(result) = stream.next().await {
210///         match result? {
211///             Message::Assistant(msg) => {
212///                 for block in &msg.message.content {
213///                     if let ContentBlock::Text(text) = block {
214///                         println!("Claude: {}", text.text);
215///                     }
216///                 }
217///             }
218///             _ => {}
219///         }
220///     }
221///
222///     Ok(())
223/// }
224/// ```
225pub async fn query_stream_with_content(
226    content: impl Into<Vec<UserContentBlock>>,
227    options: Option<ClaudeAgentOptions>,
228) -> Result<Pin<Box<dyn Stream<Item = Result<Message>> + Send>>> {
229    let content_blocks = content.into();
230    UserContentBlock::validate_content(&content_blocks)?;
231
232    let query_prompt = QueryPrompt::Content(content_blocks);
233    let opts = options.unwrap_or_default();
234
235    let mut transport = SubprocessTransport::new(query_prompt, opts)?;
236    transport.connect().await?;
237
238    let stream = async_stream::stream! {
239        let mut message_stream = transport.read_messages();
240        while let Some(json_result) = message_stream.next().await {
241            match json_result {
242                Ok(json) => {
243                    match MessageParser::parse(json) {
244                        Ok(message) => yield Ok(message),
245                        Err(e) => {
246                            yield Err(e);
247                            break;
248                        }
249                    }
250                }
251                Err(e) => {
252                    yield Err(e);
253                    break;
254                }
255            }
256        }
257    };
258
259    Ok(Box::pin(stream))
260}