Skip to main content

open_agent/
lib.rs

1//! # Open Agent SDK - Rust Implementation
2//!
3//! A production-ready, streaming-first Rust SDK for building AI agents over two wire
4//! protocols: OpenAI chat completions and Anthropic messages.
5//!
6//! ## Overview
7//!
8//! The protocol is a property of the endpoint. Select it with
9//! `AgentOptions::builder().protocol(..)`; it defaults to [`ApiProtocol::OpenAiChat`].
10//!
11//! [`ApiProtocol::OpenAiChat`] posts to `{base_url}/chat/completions` with bearer auth:
12//! - LM Studio, Ollama, llama.cpp, vLLM, and other local servers
13//! - OpenAI, OpenRouter, z.ai, and other hosted OpenAI-compatible endpoints
14//!
15//! [`ApiProtocol::Anthropic`] posts to `{base_url}/messages` with `x-api-key` and
16//! `anthropic-version`:
17//! - Anthropic
18//! - Moonshot Kimi for Coding, MiniMax, and other Anthropic-shaped endpoints
19//!
20//! ## Key Features
21//!
22//! - **Two Wire Protocols**: OpenAI chat completions or Anthropic messages, per endpoint
23//! - **Local or Hosted**: Zero-cost inference on your own hardware, or a vendor endpoint
24//! - **High Performance**: Native async/await with Tokio runtime
25//! - **Streaming Responses**: Text and reasoning reach the caller fragment by fragment,
26//!   while the stream is still open
27//! - **Finish Reasons**: Every stream reports why generation stopped
28//! - **Reasoning Channel**: Extended thinking and reasoning deltas kept out of content
29//! - **Tool Calling**: Define and execute tools with automatic schema generation
30//! - **Lifecycle Hooks**: Intercept and control execution at key points
31//! - **Interrupts**: Gracefully cancel long-running operations
32//! - **Context Management**: Manual token estimation and history truncation
33//! - **Retry Logic**: Exponential backoff with jitter for reliability
34//!
35//! ## Two Interaction Modes
36//!
37//! ### 1. Simple Query Function (`query()`)
38//! For single-turn interactions without conversation state:
39//!
40//! ```rust,no_run
41//! use open_agent::{query, AgentOptions, ContentBlock, FinishReason, StreamEvent};
42//! use futures::StreamExt;
43//!
44//! #[tokio::main]
45//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
46//!     // Configure the agent with required settings
47//!     let options = AgentOptions::builder()
48//!         .system_prompt("You are a helpful assistant")
49//!         .model("qwen2.5-32b-instruct")
50//!         .base_url("http://localhost:1234/v1")
51//!         .build()?;
52//!
53//!     // Send a single query and stream the response
54//!     let mut stream = query("What's the capital of France?", &options).await?;
55//!
56//!     // Process each event as it arrives; the stream always ends with one Finish
57//!     while let Some(event) = stream.next().await {
58//!         match event? {
59//!             StreamEvent::Block(ContentBlock::Text(text_block)) => {
60//!                 print!("{}", text_block.text);
61//!             }
62//!             StreamEvent::Block(ContentBlock::ToolUse(tool_block)) => {
63//!                 println!("Tool called: {}", tool_block.name());
64//!             }
65//!             StreamEvent::Finish(FinishReason::Length) => {
66//!                 eprintln!("response was truncated at the token cap");
67//!             }
68//!             _ => {}
69//!         }
70//!     }
71//!
72//!     Ok(())
73//! }
74//! ```
75//!
76//! ### 2. Client Object (`Client`)
77//! For multi-turn conversations with persistent state:
78//!
79//! ```rust,no_run
80//! use open_agent::{Client, AgentOptions, ContentBlock};
81//! use futures::StreamExt;
82//!
83//! #[tokio::main]
84//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
85//!     let options = AgentOptions::builder()
86//!         .system_prompt("You are a helpful assistant")
87//!         .model("qwen2.5-32b-instruct")
88//!         .base_url("http://localhost:1234/v1")
89//!         .build()?;
90//!
91//!     // Create a stateful client that maintains conversation history
92//!     let mut client = Client::new(options)?;
93//!
94//!     // First turn
95//!     client.send("What's 2+2?").await?;
96//!     while let Some(block) = client.receive().await? {
97//!         match block {
98//!             ContentBlock::Text(text) => print!("{}", text.text),
99//!             ContentBlock::ToolUse(_) | ContentBlock::ToolResult(_) | ContentBlock::Image(_) => {}
100//!         }
101//!     }
102//!
103//!     // Second turn - client remembers previous context
104//!     client.send("What about if we multiply that by 3?").await?;
105//!     while let Some(block) = client.receive().await? {
106//!         match block {
107//!             ContentBlock::Text(text) => print!("{}", text.text),
108//!             ContentBlock::ToolUse(_) | ContentBlock::ToolResult(_) | ContentBlock::Image(_) => {}
109//!         }
110//!     }
111//!
112//!     Ok(())
113//! }
114//! ```
115//!
116//! ## Architecture
117//!
118//! The SDK is organized into several modules, each with a specific responsibility:
119//!
120//! - **client**: Core streaming query engine and multi-turn client, and the transport
121//!   boundary where the protocol is applied
122//! - **types**: Data structures for messages, content blocks, configuration, and the
123//!   OpenAI and Anthropic wire formats
124//! - **tools**: Tool definition system with automatic JSON schema generation
125//! - **hooks**: Lifecycle event system for intercepting execution
126//! - **config**: Provider-specific configuration helpers
127//! - **error**: Comprehensive error types and conversions
128//! - **context**: Token estimation and message truncation utilities
129//! - **retry**: Exponential backoff retry logic with jitter
130//! - **utils**: Internal utilities for SSE parsing and tool aggregation
131
132// ============================================================================
133// MODULE DECLARATIONS
134// ============================================================================
135// These modules are private (internal implementation details) unless explicitly
136// re-exported through `pub use` statements below.
137
138/// Core client implementation providing streaming queries and stateful conversations.
139/// Contains the `query()` function for single-turn queries and `Client` struct
140/// for multi-turn conversations with automatic state management.
141mod client;
142
143/// Provider configuration helpers for LM Studio, Ollama, llama.cpp, and vLLM.
144/// Simplifies endpoint and model name resolution with environment variable support.
145mod config;
146
147/// Context window management utilities for token estimation and history truncation.
148/// Provides manual control over conversation memory to prevent context overflow.
149mod context;
150
151/// Error types and conversions for comprehensive error handling throughout the SDK.
152/// Defines the `Error` enum and `Result<T>` type alias used across all public APIs.
153mod error;
154
155/// Lifecycle hooks system for intercepting and controlling execution at key points.
156/// Enables security gates, audit logging, input/output modification, and compliance checks.
157mod hooks;
158
159/// Tool definition and execution system with automatic JSON schema generation.
160/// Allows LLMs to call Rust functions with type-safe parameter handling.
161mod tools;
162
163/// Core type definitions for messages, content blocks, and agent configuration.
164/// Includes builder patterns for ergonomic configuration, the `ApiProtocol` selector, and
165/// the OpenAI and Anthropic wire formats.
166mod types;
167
168/// Internal utilities for Server-Sent Events (SSE) parsing and tool call aggregation.
169/// Handles the low-level details of streaming response parsing for both protocols.
170mod utils;
171
172// ============================================================================
173// PUBLIC EXPORTS
174// ============================================================================
175// These items form the public API of the SDK. Everything else is internal.
176
177/// Retry utilities with exponential backoff and jitter.
178/// Made public as a module so users can access retry configuration and functions
179/// for their own operations that need retry logic.
180pub mod retry;
181
182// --- Core Client API ---
183
184pub use client::{Client, EventStream, query};
185
186// --- Stream Assembly ---
187//
188// The counterpart to fragment-by-fragment delivery: the join the SDK applies before writing a
189// turn to history, exported so a caller holding collected blocks can apply the same one.
190
191pub use utils::coalesce_text_blocks;
192
193// --- Provider Configuration ---
194
195pub use config::{Provider, get_base_url, get_model};
196
197// --- Context Management ---
198
199pub use context::{estimate_tokens, is_approaching_limit, truncate_messages};
200
201// --- Error Handling ---
202
203pub use error::{Error, Result};
204
205// --- Lifecycle Hooks ---
206
207pub use hooks::{
208    HOOK_POST_TOOL_USE, HOOK_PRE_TOOL_USE, HOOK_USER_PROMPT_SUBMIT, HookDecision, Hooks,
209    PostToolUseEvent, PreToolUseEvent, UserPromptSubmitEvent,
210};
211
212// --- Tool System ---
213
214pub use tools::{Tool, ToolBuilder, tool};
215
216// --- Core Types ---
217
218pub use types::{
219    AgentOptions, AgentOptionsBuilder, ApiProtocol, BaseUrl, ContentBlock, FinishReason,
220    ImageBlock, ImageDetail, Message, MessageRole, ModelName, OpenAIContent, OpenAIContentPart,
221    StreamEvent, Temperature, TextBlock, ToolResultBlock, ToolUseBlock,
222};
223
224// --- Anthropic Wire Format ---
225//
226// Exported for the same reason the OpenAI wire types are: a caller building a gateway, a
227// recording proxy, or a test double needs to name what goes over the wire.
228
229pub use types::{
230    AnthropicBlockStart, AnthropicDelta, AnthropicErrorBody, AnthropicEvent, AnthropicMessage,
231    AnthropicMessageDelta, AnthropicRequest, anthropic_finish_reason,
232};
233
234// `AnthropicRequest::from_openai` takes an `OpenAIRequest`, so the request half of the
235// OpenAI wire format has to be nameable for that constructor to be callable at all.
236pub use types::{OpenAIFunction, OpenAIMessage, OpenAIRequest, OpenAIToolCall};
237
238// ============================================================================
239// CONVENIENCE PRELUDE
240// ============================================================================
241
242/// Convenience module containing the most commonly used types and functions.
243/// Import with `use open_agent::prelude::*;` to get everything you need for typical usage.
244///
245/// This includes:
246/// - Configuration: AgentOptions, AgentOptionsBuilder, ApiProtocol
247/// - Client: Client, query()
248/// - Content: ContentBlock, TextBlock, ToolUseBlock
249/// - Streaming: StreamEvent, FinishReason
250/// - Tools: Tool, tool()
251/// - Hooks: Hooks, HookDecision, hook event types
252/// - Errors: Error, Result
253pub mod prelude {
254    pub use crate::{
255        AgentOptions, AgentOptionsBuilder, ApiProtocol, BaseUrl, Client, ContentBlock, Error,
256        FinishReason, HookDecision, Hooks, ModelName, PostToolUseEvent, PreToolUseEvent, Result,
257        StreamEvent, Temperature, TextBlock, Tool, ToolUseBlock, UserPromptSubmitEvent, query,
258        tool,
259    };
260}