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, 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 mut events = agent.run(thread_id, "Hello!".into(), ctx);
33//!
34//! // 4. Process streaming events
35//! while let Some(event) = events.recv().await {
36//! match event {
37//! AgentEvent::Text { text } => print!("{text}"),
38//! AgentEvent::Done { .. } => break,
39//! _ => {}
40//! }
41//! }
42//! # Ok(())
43//! # }
44//! ```
45//!
46//! ## Core Concepts
47//!
48//! ### Agent Loop
49//!
50//! The [`AgentLoop`] orchestrates the conversation cycle:
51//!
52//! 1. User sends a message
53//! 2. Agent sends message to LLM
54//! 3. LLM responds with text and/or tool calls
55//! 4. Agent executes tools and feeds results back to LLM
56//! 5. Repeat until LLM responds with only text
57//!
58//! Use [`builder()`] to construct an agent:
59//!
60//! ```no_run
61//! use agent_sdk::{builder, AgentConfig, providers::AnthropicProvider};
62//!
63//! # fn example() {
64//! # let api_key = String::new();
65//! let agent = builder::<()>()
66//! .provider(AnthropicProvider::sonnet(api_key))
67//! .config(AgentConfig {
68//! max_turns: 20,
69//! system_prompt: "You are a helpful assistant.".into(),
70//! ..Default::default()
71//! })
72//! .build();
73//! # }
74//! ```
75//!
76//! ### Tools
77//!
78//! Tools let the LLM interact with external systems. Implement the [`Tool`] trait:
79//!
80//! ```
81//! use agent_sdk::{Tool, ToolContext, ToolResult, ToolTier};
82//! use async_trait::async_trait;
83//! use serde_json::{json, Value};
84//!
85//! struct WeatherTool;
86//!
87//! #[async_trait]
88//! impl Tool<()> for WeatherTool {
89//! fn name(&self) -> &str { "get_weather" }
90//!
91//! fn description(&self) -> &str {
92//! "Get current weather for a city"
93//! }
94//!
95//! fn input_schema(&self) -> Value {
96//! json!({
97//! "type": "object",
98//! "properties": {
99//! "city": { "type": "string" }
100//! },
101//! "required": ["city"]
102//! })
103//! }
104//!
105//! fn tier(&self) -> ToolTier { ToolTier::Observe }
106//!
107//! async fn execute(
108//! &self,
109//! _ctx: &ToolContext<()>,
110//! input: Value,
111//! ) -> anyhow::Result<ToolResult> {
112//! let city = input["city"].as_str().unwrap_or("Unknown");
113//! Ok(ToolResult::success(format!("Weather in {city}: Sunny, 72°F")))
114//! }
115//! }
116//! ```
117//!
118//! Register tools with [`ToolRegistry`]:
119//!
120//! ```no_run
121//! use agent_sdk::{builder, ToolRegistry, providers::AnthropicProvider};
122//! # use agent_sdk::{Tool, ToolContext, ToolResult, ToolTier};
123//! # use async_trait::async_trait;
124//! # use serde_json::Value;
125//! # struct WeatherTool;
126//! # #[async_trait]
127//! # impl Tool<()> for WeatherTool {
128//! # fn name(&self) -> &str { "weather" }
129//! # fn description(&self) -> &str { "" }
130//! # fn input_schema(&self) -> Value { Value::Null }
131//! # async fn execute(&self, _: &ToolContext<()>, _: Value) -> anyhow::Result<ToolResult> {
132//! # Ok(ToolResult::success(""))
133//! # }
134//! # }
135//!
136//! # fn example() {
137//! # let api_key = String::new();
138//! let mut tools = ToolRegistry::new();
139//! tools.register(WeatherTool);
140//!
141//! let agent = builder::<()>()
142//! .provider(AnthropicProvider::sonnet(api_key))
143//! .tools(tools)
144//! .build();
145//! # }
146//! ```
147//!
148//! ### Tool Tiers
149//!
150//! Tools are classified by permission level via [`ToolTier`]:
151//!
152//! | Tier | Description | Example |
153//! |------|-------------|---------|
154//! | [`ToolTier::Observe`] | Read-only, always allowed | Get balance, read file |
155//! | [`ToolTier::Confirm`] | Requires user confirmation | Send email, write file |
156//! | [`ToolTier::RequiresPin`] | Requires PIN verification | Transfer funds |
157//!
158//! ### Lifecycle Hooks
159//!
160//! Implement [`AgentHooks`] to intercept and control agent behavior:
161//!
162//! ```
163//! use agent_sdk::{AgentHooks, ToolDecision, ToolResult, ToolTier};
164//! use async_trait::async_trait;
165//! use serde_json::Value;
166//!
167//! struct MyHooks;
168//!
169//! #[async_trait]
170//! impl AgentHooks for MyHooks {
171//! async fn pre_tool_use(
172//! &self,
173//! tool_name: &str,
174//! _input: &Value,
175//! tier: ToolTier,
176//! ) -> ToolDecision {
177//! println!("Tool called: {tool_name}");
178//! match tier {
179//! ToolTier::Observe => ToolDecision::Allow,
180//! ToolTier::Confirm => ToolDecision::RequiresConfirmation(
181//! "Please confirm this action".into()
182//! ),
183//! ToolTier::RequiresPin => ToolDecision::RequiresPin(
184//! "Enter PIN to continue".into()
185//! ),
186//! }
187//! }
188//!
189//! async fn post_tool_use(&self, tool_name: &str, result: &ToolResult) {
190//! println!("{tool_name} completed: {}", result.success);
191//! }
192//! }
193//! ```
194//!
195//! Built-in hook implementations:
196//! - [`DefaultHooks`] - Tier-based permissions (default)
197//! - [`AllowAllHooks`] - Allow all tools without confirmation (for testing)
198//! - [`LoggingHooks`] - Debug logging for all events
199//!
200//! ### Events
201//!
202//! The agent emits [`AgentEvent`]s during execution for real-time updates:
203//!
204//! | Event | Description |
205//! |-------|-------------|
206//! | [`AgentEvent::Start`] | Agent begins processing |
207//! | [`AgentEvent::Text`] | Text response from LLM |
208//! | [`AgentEvent::TextDelta`] | Streaming text chunk |
209//! | [`AgentEvent::ToolCallStart`] | Tool execution starting |
210//! | [`AgentEvent::ToolCallEnd`] | Tool execution completed |
211//! | [`AgentEvent::TurnComplete`] | One LLM round-trip finished |
212//! | [`AgentEvent::Done`] | Agent completed successfully |
213//! | [`AgentEvent::Error`] | An error occurred |
214//!
215//! ### Task Tracking
216//!
217//! Use [`TodoWriteTool`] and [`TodoReadTool`] to track task progress:
218//!
219//! ```no_run
220//! use agent_sdk::todo::{TodoState, TodoWriteTool, TodoReadTool};
221//! use std::sync::Arc;
222//! use tokio::sync::RwLock;
223//!
224//! let state = Arc::new(RwLock::new(TodoState::new()));
225//! let write_tool = TodoWriteTool::new(Arc::clone(&state));
226//! let read_tool = TodoReadTool::new(state);
227//! ```
228//!
229//! Task states: `Pending` (○), `InProgress` (⚡), `Completed` (✓)
230//!
231//! ### Custom Context
232//!
233//! Pass application-specific data to tools via the generic type parameter:
234//!
235//! ```
236//! use agent_sdk::{Tool, ToolContext, ToolResult, ToolTier};
237//! use async_trait::async_trait;
238//! use serde_json::Value;
239//!
240//! // Your application context
241//! struct AppContext {
242//! user_id: String,
243//! // database: Database,
244//! }
245//!
246//! struct UserInfoTool;
247//!
248//! #[async_trait]
249//! impl Tool<AppContext> for UserInfoTool {
250//! fn name(&self) -> &str { "get_user_info" }
251//! fn description(&self) -> &str { "Get info about current user" }
252//! fn input_schema(&self) -> Value { serde_json::json!({"type": "object"}) }
253//!
254//! async fn execute(
255//! &self,
256//! ctx: &ToolContext<AppContext>,
257//! _input: Value,
258//! ) -> anyhow::Result<ToolResult> {
259//! // Access your context
260//! let user_id = &ctx.app.user_id;
261//! Ok(ToolResult::success(format!("User: {user_id}")))
262//! }
263//! }
264//! ```
265//!
266//! ## Modules
267//!
268//! | Module | Description |
269//! |--------|-------------|
270//! | [`providers`] | LLM provider implementations |
271//! | [`primitive_tools`] | Built-in file operation tools (Read, Write, Edit, Glob, Grep, Bash) |
272//! | [`llm`] | LLM abstraction layer |
273//! | [`subagent`] | Nested agent execution with [`SubagentFactory`] |
274//! | [`mcp`] | Model Context Protocol support |
275//! | [`todo`] | Task tracking tools ([`TodoWriteTool`], [`TodoReadTool`]) |
276//! | [`user_interaction`] | User question/confirmation tools ([`AskUserQuestionTool`]) |
277//! | [`web`] | Web search and fetch tools |
278//! | [`skills`] | Custom skill/command loading |
279//!
280//! ## Feature Flags
281//!
282//! All features are enabled by default. The crate has no optional features currently.
283
284#![forbid(unsafe_code)]
285
286mod agent_loop;
287mod capabilities;
288pub mod context;
289mod environment;
290mod events;
291mod filesystem;
292mod hooks;
293pub mod llm;
294pub mod mcp;
295pub mod primitive_tools;
296pub mod providers;
297pub mod skills;
298mod stores;
299pub mod subagent;
300pub mod todo;
301mod tools;
302mod types;
303pub mod user_interaction;
304pub mod web;
305
306pub use agent_loop::{AgentLoop, AgentLoopBuilder, builder};
307pub use capabilities::AgentCapabilities;
308pub use environment::{Environment, ExecResult, FileEntry, GrepMatch, NullEnvironment};
309pub use events::AgentEvent;
310pub use filesystem::{InMemoryFileSystem, LocalFileSystem};
311pub use hooks::{AgentHooks, AllowAllHooks, DefaultHooks, LoggingHooks, ToolDecision};
312pub use llm::LlmProvider;
313pub use stores::{InMemoryStore, MessageStore, StateStore};
314pub use tools::{Tool, ToolContext, ToolRegistry};
315pub use types::{
316 AgentConfig, AgentState, PendingAction, RetryConfig, ThreadId, TokenUsage, ToolResult, ToolTier,
317};
318
319// Re-export user interaction types for convenience
320pub use user_interaction::{
321 AskUserQuestionTool, ConfirmationRequest, ConfirmationResponse, QuestionOption,
322 QuestionRequest, QuestionResponse,
323};
324
325// Re-export subagent types for convenience
326pub use subagent::{SubagentConfig, SubagentFactory, SubagentTool};
327
328// Re-export todo types for convenience
329pub use todo::{TodoItem, TodoReadTool, TodoState, TodoStatus, TodoWriteTool};