Skip to main content

agent_sdk/
lib.rs

1//! # Agent SDK
2//!
3//! A Rust SDK for building AI agents powered by large language models (LLMs).
4//!
5//! This crate provides the infrastructure to build agents that can:
6//! - Converse with users via multiple LLM providers
7//! - Execute tools to interact with external systems
8//! - Stream events in real-time for responsive UIs
9//! - Persist conversation history and state
10//!
11//! ## Quick Start
12//!
13//! ```no_run
14//! use agent_sdk::{
15//!     builder, AgentEvent, AgentInput, CancellationToken, ThreadId, ToolContext,
16//!     providers::AnthropicProvider,
17//! };
18//!
19//! # async fn example() -> anyhow::Result<()> {
20//! // 1. Create an LLM provider
21//! let api_key = std::env::var("ANTHROPIC_API_KEY")?;
22//! let provider = AnthropicProvider::sonnet(api_key);
23//!
24//! // 2. Build the agent
25//! let agent = builder::<()>()
26//!     .provider(provider)
27//!     .build();
28//!
29//! // 3. Run a conversation
30//! let thread_id = ThreadId::new();
31//! let ctx = ToolContext::new(());
32//! let cancel = CancellationToken::new();
33//! let (mut events, _final_state) = agent.run(
34//!     thread_id,
35//!     AgentInput::Text("Hello!".to_string()),
36//!     ctx,
37//!     cancel,
38//! );
39//!
40//! // 4. Process streaming events
41//! while let Some(envelope) = events.recv().await {
42//!     match envelope.event {
43//!         AgentEvent::Text { message_id: _, text } => print!("{text}"),
44//!         AgentEvent::Done { .. } => break,
45//!         _ => {}
46//!     }
47//! }
48//! # Ok(())
49//! # }
50//! ```
51//!
52//! ## Core Concepts
53//!
54//! ### Agent Loop
55//!
56//! The [`AgentLoop`] orchestrates the conversation cycle:
57//!
58//! 1. User sends a message
59//! 2. Agent sends message to LLM
60//! 3. LLM responds with text and/or tool calls
61//! 4. Agent executes tools and feeds results back to LLM
62//! 5. Repeat until LLM responds with only text
63//!
64//! Use [`builder()`] to construct an agent:
65//!
66//! ```no_run
67//! use agent_sdk::{builder, AgentConfig, providers::AnthropicProvider};
68//!
69//! # fn example() {
70//! # let api_key = String::new();
71//! let agent = builder::<()>()
72//!     .provider(AnthropicProvider::sonnet(api_key))
73//!     .config(AgentConfig {
74//!         max_turns: Some(20),
75//!         system_prompt: "You are a helpful assistant.".into(),
76//!         ..Default::default()
77//!     })
78//!     .build();
79//! # }
80//! ```
81//!
82//! ### Tools
83//!
84//! Tools let the LLM interact with external systems. Implement the [`Tool`] trait:
85//!
86//! ```
87//! use agent_sdk::{DynamicToolName, Tool, ToolContext, ToolResult, ToolTier};
88//! use serde_json::{json, Value};
89//! use std::future::Future;
90//!
91//! struct WeatherTool;
92//!
93//! // No #[async_trait] needed - Rust 1.75+ supports native async traits
94//! impl Tool<()> for WeatherTool {
95//!     type Name = DynamicToolName;
96//!
97//!     fn name(&self) -> DynamicToolName { DynamicToolName::new("get_weather") }
98//!
99//!     fn display_name(&self) -> &'static str { "Weather" }
100//!
101//!     fn description(&self) -> &'static str {
102//!         "Get current weather for a city"
103//!     }
104//!
105//!     fn input_schema(&self) -> Value {
106//!         json!({
107//!             "type": "object",
108//!             "properties": {
109//!                 "city": { "type": "string" }
110//!             },
111//!             "required": ["city"]
112//!         })
113//!     }
114//!
115//!     fn tier(&self) -> ToolTier { ToolTier::Observe }
116//!
117//!     fn execute(
118//!         &self,
119//!         _ctx: &ToolContext<()>,
120//!         input: Value,
121//!     ) -> impl Future<Output = anyhow::Result<ToolResult>> + Send {
122//!         async move {
123//!             let city = input["city"].as_str().unwrap_or("Unknown");
124//!             Ok(ToolResult::success(format!("Weather in {city}: Sunny, 72°F")))
125//!         }
126//!     }
127//! }
128//! ```
129//!
130//! Register tools with [`ToolRegistry`]:
131//!
132//! ```no_run
133//! use agent_sdk::{builder, DynamicToolName, ToolRegistry, providers::AnthropicProvider};
134//! # use agent_sdk::{Tool, ToolContext, ToolResult, ToolTier};
135//! # use serde_json::Value;
136//! # use std::future::Future;
137//! # struct WeatherTool;
138//! # impl Tool<()> for WeatherTool {
139//! #     type Name = DynamicToolName;
140//! #     fn name(&self) -> DynamicToolName { DynamicToolName::new("weather") }
141//! #     fn display_name(&self) -> &'static str { "" }
142//! #     fn description(&self) -> &'static str { "" }
143//! #     fn input_schema(&self) -> Value { Value::Null }
144//! #     fn execute(&self, _: &ToolContext<()>, _: Value) -> impl Future<Output = anyhow::Result<ToolResult>> + Send {
145//! #         async { Ok(ToolResult::success("")) }
146//! #     }
147//! # }
148//!
149//! # fn example() {
150//! # let api_key = String::new();
151//! let mut tools = ToolRegistry::new();
152//! tools.register(WeatherTool);
153//!
154//! let agent = builder::<()>()
155//!     .provider(AnthropicProvider::sonnet(api_key))
156//!     .tools(tools)
157//!     .build();
158//! # }
159//! ```
160//!
161//! ### Tool Tiers
162//!
163//! Tools are classified by permission level via [`ToolTier`]:
164//!
165//! | Tier | Description | Example |
166//! |------|-------------|---------|
167//! | [`ToolTier::Observe`] | Read-only, always allowed | Get balance, read file |
168//! | [`ToolTier::Confirm`] | Requires user confirmation | Send email, transfer funds |
169//!
170//! ### Lifecycle Hooks
171//!
172//! Implement [`AgentHooks`] to intercept and control agent behavior:
173//!
174//! ```
175//! use agent_sdk::{AgentHooks, ToolDecision, ToolResult, ToolTier};
176//! use async_trait::async_trait;
177//! use serde_json::Value;
178//!
179//! struct MyHooks;
180//!
181//! #[async_trait]
182//! impl AgentHooks for MyHooks {
183//!     async fn pre_tool_use(
184//!         &self,
185//!         tool_name: &str,
186//!         _input: &Value,
187//!         tier: ToolTier,
188//!     ) -> ToolDecision {
189//!         println!("Tool called: {tool_name}");
190//!         match tier {
191//!             ToolTier::Observe => ToolDecision::Allow,
192//!             ToolTier::Confirm => ToolDecision::RequiresConfirmation(
193//!                 "Please confirm this action".into()
194//!             ),
195//!         }
196//!     }
197//!
198//!     async fn post_tool_use(&self, tool_name: &str, result: &ToolResult) {
199//!         println!("{tool_name} completed: {}", result.success);
200//!     }
201//! }
202//! ```
203//!
204//! Built-in hook implementations:
205//! - [`DefaultHooks`] - Tier-based permissions (default)
206//! - [`AllowAllHooks`] - Allow all tools without confirmation (for testing)
207//! - [`LoggingHooks`] - Debug logging for all events
208//!
209//! ### Events
210//!
211//! The agent emits [`AgentEvent`]s during execution for real-time updates:
212//!
213//! | Event | Description |
214//! |-------|-------------|
215//! | [`AgentEvent::Start`] | Agent begins processing |
216//! | [`AgentEvent::Text`] | Text response from LLM |
217//! | [`AgentEvent::TextDelta`] | Streaming text chunk |
218//! | [`AgentEvent::ToolCallStart`] | Tool execution starting |
219//! | [`AgentEvent::ToolCallEnd`] | Tool execution completed |
220//! | [`AgentEvent::TurnComplete`] | One LLM round-trip finished |
221//! | [`AgentEvent::Done`] | Agent completed successfully |
222//! | [`AgentEvent::Error`] | An error occurred |
223//!
224//! ### Task Tracking
225//!
226//! Use [`TodoWriteTool`] and [`TodoReadTool`] to track task progress:
227//!
228//! ```no_run
229//! use agent_sdk::todo::{TodoState, TodoWriteTool, TodoReadTool};
230//! use std::sync::Arc;
231//! use tokio::sync::RwLock;
232//!
233//! let state = Arc::new(RwLock::new(TodoState::new()));
234//! let write_tool = TodoWriteTool::new(Arc::clone(&state));
235//! let read_tool = TodoReadTool::new(state);
236//! ```
237//!
238//! Task states: `Pending` (○), `InProgress` (⚡), `Completed` (✓)
239//!
240//! ### Custom Context
241//!
242//! Pass application-specific data to tools via the generic type parameter:
243//!
244//! ```
245//! use agent_sdk::{DynamicToolName, Tool, ToolContext, ToolResult, ToolTier};
246//! use serde_json::Value;
247//! use std::future::Future;
248//!
249//! // Your application context
250//! struct AppContext {
251//!     user_id: String,
252//!     // database: Database,
253//! }
254//!
255//! struct UserInfoTool;
256//!
257//! impl Tool<AppContext> for UserInfoTool {
258//!     type Name = DynamicToolName;
259//!
260//!     fn name(&self) -> DynamicToolName { DynamicToolName::new("get_user_info") }
261//!     fn display_name(&self) -> &'static str { "User Info" }
262//!     fn description(&self) -> &'static str { "Get info about current user" }
263//!     fn input_schema(&self) -> Value { serde_json::json!({"type": "object"}) }
264//!
265//!     fn execute(
266//!         &self,
267//!         ctx: &ToolContext<AppContext>,
268//!         _input: Value,
269//!     ) -> impl Future<Output = anyhow::Result<ToolResult>> + Send {
270//!         let user_id = ctx.app.user_id.clone();
271//!         async move {
272//!             Ok(ToolResult::success(format!("User: {user_id}")))
273//!         }
274//!     }
275//! }
276//! ```
277//!
278//! ## Modules
279//!
280//! | Module | Description |
281//! |--------|-------------|
282//! | [`providers`] | LLM provider implementations |
283//! | [`primitive_tools`] | Built-in file operation tools (Read, Write, Edit, Glob, Grep, Bash) |
284//! | [`llm`] | LLM abstraction layer |
285//! | [`subagent`] | Nested agent execution with [`SubagentFactory`] |
286//! | [`mcp`] | Model Context Protocol support |
287//! | [`todo`] | Task tracking tools ([`TodoWriteTool`], [`TodoReadTool`]) |
288//! | [`user_interaction`] | User question/confirmation tools ([`AskUserQuestionTool`]) |
289//! | [`web`] | Web search and fetch tools |
290//! | [`skills`] | Custom skill/command loading |
291//! | [`reminders`] | System reminder infrastructure for agent guidance |
292//!
293//! ## System Reminders
294//!
295//! The SDK includes a reminder system that provides contextual guidance to the AI agent
296//! using the `<system-reminder>` XML tag pattern. Claude is trained to recognize these
297//! tags and follow the instructions without mentioning them to users.
298//!
299//! ```
300//! use agent_sdk::reminders::{wrap_reminder, ReminderConfig, ReminderTracker};
301//!
302//! // Wrap guidance in system-reminder tags
303//! let reminder = wrap_reminder("Verify the output before proceeding.");
304//!
305//! // Configure reminder behavior
306//! let config = ReminderConfig::new()
307//!     .with_todo_reminder_turns(5)
308//!     .with_repeated_action_threshold(3);
309//! ```
310//!
311//! ## Feature Flags
312//!
313//! All features are enabled by default. The crate has no optional features currently.
314
315#![forbid(unsafe_code)]
316
317mod agent_loop;
318mod capabilities;
319pub mod context;
320mod environment;
321mod events;
322mod filesystem;
323mod hooks;
324pub mod llm;
325pub mod mcp;
326pub mod model_capabilities;
327pub mod primitive_tools;
328pub mod providers;
329pub mod reminders;
330pub mod skills;
331mod stores;
332pub mod subagent;
333pub mod todo;
334mod tools;
335mod types;
336pub mod user_interaction;
337pub mod web;
338
339pub use agent_loop::{AgentLoop, AgentLoopBuilder, builder};
340pub use capabilities::AgentCapabilities;
341pub use environment::{Environment, ExecResult, FileEntry, GrepMatch, NullEnvironment};
342pub use events::{AgentEvent, AgentEventEnvelope, SequenceCounter};
343pub use filesystem::{InMemoryFileSystem, LocalFileSystem};
344
345// Re-export CancellationToken for use with `AgentLoop::run_with_cancel`.
346pub use hooks::{AgentHooks, AllowAllHooks, DefaultHooks, LoggingHooks, ToolDecision};
347pub use llm::{ContentBlock, ContentSource, Effort, LlmProvider, ThinkingConfig, ThinkingMode};
348pub use model_capabilities::{
349    ModelCapabilities, PricePoint, Pricing, SourceStatus, get_model_capabilities,
350    supported_model_capabilities,
351};
352pub use stores::{
353    InMemoryExecutionStore, InMemoryStore, MessageStore, StateStore, ToolExecutionStore,
354};
355pub use tokio_util::sync::CancellationToken;
356pub use tools::{
357    AsyncTool, DynamicToolName, ErasedAsyncTool, ErasedListenTool, ErasedTool, ErasedToolStatus,
358    ListenExecuteTool, ListenStopReason, ListenToolUpdate, PrimitiveToolName, ProgressStage, Tool,
359    ToolContext, ToolName, ToolRegistry, ToolStatus, stage_to_string, tool_name_from_str,
360    tool_name_to_string,
361};
362pub use types::{
363    AgentConfig, AgentContinuation, AgentError, AgentInput, AgentRunState, AgentState,
364    ExecutionStatus, ListenExecutionContext, PendingToolCallInfo, RetryConfig, ThreadId,
365    TokenUsage, ToolExecution, ToolOutcome, ToolResult, ToolTier, TurnOutcome,
366};
367
368// Re-export user interaction types for convenience
369pub use user_interaction::{
370    AskUserQuestionTool, ConfirmationRequest, ConfirmationResponse, QuestionOption,
371    QuestionRequest, QuestionResponse,
372};
373
374// Re-export subagent types for convenience
375pub use subagent::{SubagentConfig, SubagentFactory, SubagentTool};
376
377// Re-export todo types for convenience
378pub use todo::{TodoItem, TodoReadTool, TodoState, TodoStatus, TodoWriteTool};
379
380// Re-export reminder types for convenience
381pub use reminders::{
382    ReminderConfig, ReminderTracker, ReminderTrigger, ToolReminder, append_reminder, wrap_reminder,
383};