add examples, end-to-end tests, and Cargo.lock

Three examples:
- basic.rs: query() one-shot prompt + cost reporting.
- with_options.rs: system_prompt, model selection, effort, permission mode.
- interactive.rs: Client multi-turn session with two back-to-back sends.

Integration tests in tests/transport_end_to_end.rs spawn a tiny POSIX
shell script (tests/fake_cli/fake-claude.sh) as a stand-in for the real
claude CLI. The fake answers -v with a version, reads one user-message
frame on stdin, and emits a fixed assistant + result pair on stdout, then
blocks on stdin until close. This proves the spawn / write / read /
disconnect lifecycle works without an authenticated claude install.

Coverage:
- query() round-trip: stream yields Assistant then Result.
- Client round-trip: connect + send + drain to Result + disconnect.
- CLI-not-found surfaces as a typed Error::CliNotFound.

Cargo.lock is committed since this is, in practice, both a library and a
binary (the examples link the crate). Locking dev-deps avoids surprise
churn in CI.
This commit is contained in:
Kayos 2026-05-14 08:04:04 -07:00
parent ef1d8fdd31
commit 1ed4d8211f
6 changed files with 795 additions and 0 deletions

50
examples/basic.rs Normal file
View file

@ -0,0 +1,50 @@
//! Minimal `query()` example.
//!
//! Run:
//!
//! ```sh
//! cargo run --example basic
//! ```
//!
//! Requires the `claude` CLI on `PATH` and authenticated. Output prints each
//! assistant text block, then the total cost from the terminal `result`
//! message.
use claude_agent_sdk::{ClaudeAgentOptions, ContentBlock, Message, query};
use tokio_stream::StreamExt;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
tracing_subscriber::fmt()
.with_env_filter(
tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| "claude_agent_sdk=info".into()),
)
.init();
let opts = ClaudeAgentOptions::new().with_max_turns(1);
let mut stream = query("What is 2 + 2? Reply with just the digit.", opts).await?;
while let Some(item) = stream.next().await {
match item? {
Message::Assistant(a) => {
for block in &a.message.content {
if let ContentBlock::Text(t) = block {
println!("Claude: {}", t.text);
}
}
}
Message::Result(r) => {
println!(
"\n[result] subtype={} turns={} cost_usd={:?}",
r.subtype, r.num_turns, r.total_cost_usd
);
break;
}
_ => {}
}
}
Ok(())
}

52
examples/interactive.rs Normal file
View file

@ -0,0 +1,52 @@
//! Multi-turn [`Client`] example.
//!
//! Sends two prompts back-to-back without re-spawning the CLI subprocess —
//! mirrors the Python SDK's `ClaudeSDKClient` async-context pattern.
//!
//! Run:
//!
//! ```sh
//! cargo run --example interactive
//! ```
use claude_agent_sdk::{ClaudeAgentOptions, Client, ContentBlock, Message};
use tokio_stream::StreamExt;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let mut client = Client::new(ClaudeAgentOptions::new().with_max_turns(1)).await?;
client.connect().await?;
let mut stream = client.messages();
client.send("Say 'one'.").await?;
drain_until_result(&mut stream, "turn 1").await?;
client.send("Say 'two'.").await?;
drain_until_result(&mut stream, "turn 2").await?;
client.disconnect().await?;
Ok(())
}
async fn drain_until_result(
stream: &mut (impl tokio_stream::Stream<Item = claude_agent_sdk::Result<Message>> + Unpin),
label: &str,
) -> anyhow::Result<()> {
while let Some(item) = stream.next().await {
match item? {
Message::Assistant(a) => {
for block in &a.message.content {
if let ContentBlock::Text(t) = block {
println!("[{label}] Claude: {}", t.text);
}
}
}
Message::Result(_) => {
println!("[{label}] done");
return Ok(());
}
_ => {}
}
}
Ok(())
}

43
examples/with_options.rs Normal file
View file

@ -0,0 +1,43 @@
//! `query()` with a custom system prompt, model selection, and `cwd`.
//!
//! Run:
//!
//! ```sh
//! cargo run --example with_options
//! ```
use claude_agent_sdk::{
ClaudeAgentOptions, ContentBlock, Effort, Message, PermissionMode, query,
};
use tokio_stream::StreamExt;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let opts = ClaudeAgentOptions::new()
.with_system_prompt("You are a senior Rust engineer. Be terse.")
.with_model("claude-sonnet-4-5")
.with_max_turns(1)
.with_effort(Effort::Low)
.with_permission_mode(PermissionMode::Plan);
let mut stream = query(
"In one sentence, what does the `?` operator do in Rust?",
opts,
)
.await?;
while let Some(item) = stream.next().await {
match item? {
Message::Assistant(a) => {
for block in &a.message.content {
if let ContentBlock::Text(t) = block {
println!("{}", t.text);
}
}
}
Message::Result(_) => break,
_ => {}
}
}
Ok(())
}