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//! ### Custom Context
216//!
217//! Pass application-specific data to tools via the generic type parameter:
218//!
219//! ```
220//! use agent_sdk::{Tool, ToolContext, ToolResult, ToolTier};
221//! use async_trait::async_trait;
222//! use serde_json::Value;
223//!
224//! // Your application context
225//! struct AppContext {
226//!     user_id: String,
227//!     // database: Database,
228//! }
229//!
230//! struct UserInfoTool;
231//!
232//! #[async_trait]
233//! impl Tool<AppContext> for UserInfoTool {
234//!     fn name(&self) -> &str { "get_user_info" }
235//!     fn description(&self) -> &str { "Get info about current user" }
236//!     fn input_schema(&self) -> Value { serde_json::json!({"type": "object"}) }
237//!
238//!     async fn execute(
239//!         &self,
240//!         ctx: &ToolContext<AppContext>,
241//!         _input: Value,
242//!     ) -> anyhow::Result<ToolResult> {
243//!         // Access your context
244//!         let user_id = &ctx.app.user_id;
245//!         Ok(ToolResult::success(format!("User: {user_id}")))
246//!     }
247//! }
248//! ```
249//!
250//! ## Modules
251//!
252//! | Module | Description |
253//! |--------|-------------|
254//! | [`providers`] | LLM provider implementations |
255//! | [`primitive_tools`] | Built-in file operation tools (Read, Write, Edit, Glob, Grep, Bash) |
256//! | [`llm`] | LLM abstraction layer |
257//! | [`subagent`] | Nested agent execution |
258//! | [`mcp`] | Model Context Protocol support |
259//!
260//! ## Feature Flags
261//!
262//! All features are enabled by default. The crate has no optional features currently.
263
264#![forbid(unsafe_code)]
265
266mod agent_loop;
267mod capabilities;
268pub mod context;
269mod environment;
270mod events;
271mod filesystem;
272mod hooks;
273pub mod llm;
274pub mod mcp;
275pub mod primitive_tools;
276pub mod providers;
277pub mod skills;
278mod stores;
279pub mod subagent;
280mod tools;
281mod types;
282pub mod web;
283
284pub use agent_loop::{AgentLoop, AgentLoopBuilder, builder};
285pub use capabilities::AgentCapabilities;
286pub use environment::{Environment, ExecResult, FileEntry, GrepMatch, NullEnvironment};
287pub use events::AgentEvent;
288pub use filesystem::{InMemoryFileSystem, LocalFileSystem};
289pub use hooks::{AgentHooks, AllowAllHooks, DefaultHooks, LoggingHooks, ToolDecision};
290pub use llm::LlmProvider;
291pub use stores::{InMemoryStore, MessageStore, StateStore};
292pub use tools::{Tool, ToolContext, ToolRegistry};
293pub use types::{
294    AgentConfig, AgentState, PendingAction, RetryConfig, ThreadId, TokenUsage, ToolResult, ToolTier,
295};