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}