agtrace_sdk/
lib.rs

1//! agtrace-sdk: SDK for AI agent observability.
2//!
3//! **Note**: README.md is auto-generated from this rustdoc using `cargo-rdme`.
4//! To update: `cargo rdme --workspace-project agtrace-sdk`
5//!
6//! # Overview
7//!
8//! `agtrace-sdk` provides a high-level, stable API for building tools on top of agtrace.
9//! It powers agtrace's MCP server (letting agents query their execution history) and CLI tools,
10//! and can be embedded in your own applications. The SDK normalizes logs from multiple providers
11//! (Claude Code, Codex, Gemini) into a unified data model, enabling cross-provider analysis.
12//!
13//! # Quickstart
14//!
15//! ```no_run
16//! use agtrace_sdk::{Client, types::SessionFilter};
17//!
18//! # #[tokio::main]
19//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
20//! // Connect to the local workspace
21//! let client = Client::connect_default().await?;
22//!
23//! // List sessions and browse the most recent one
24//! let sessions = client.sessions().list(SessionFilter::all())?;
25//! if let Some(summary) = sessions.first() {
26//!     let handle = client.sessions().get(&summary.id)?;
27//!     let session = handle.assemble()?;
28//!
29//!     println!("Session: {} turns, {} tokens",
30//!         session.turns.len(),
31//!         session.stats.total_tokens);
32//!
33//!     // Browse tool calls
34//!     for turn in &session.turns {
35//!         for step in &turn.steps {
36//!             for tool in &step.tools {
37//!                 println!("  {} ({})",
38//!                     tool.call.content.name(),
39//!                     if tool.is_error { "failed" } else { "ok" });
40//!             }
41//!         }
42//!     }
43//! }
44//! # Ok(())
45//! # }
46//! ```
47//!
48//! For complete examples, see the [`examples/`](https://github.com/lanegrid/agtrace/tree/main/crates/agtrace-sdk/examples) directory.
49//!
50//! # Architecture
51//!
52//! This SDK acts as a facade over:
53//! - `agtrace-types`: Core domain models (AgentEvent, etc.)
54//! - `agtrace-providers`: Multi-provider log normalization
55//! - `agtrace-engine`: Session assembly and analysis
56//! - `agtrace-index`: Metadata storage and querying
57//! - `agtrace-runtime`: Internal orchestration layer
58//!
59//! # Usage Patterns
60//!
61//! ## Session Browsing
62//!
63//! Access structured session data (Turn → Step → Tool hierarchy):
64//!
65//! ```no_run
66//! use agtrace_sdk::{Client, types::SessionFilter};
67//!
68//! # #[tokio::main]
69//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
70//! let client = Client::connect_default().await?;
71//! let sessions = client.sessions().list(SessionFilter::all())?;
72//!
73//! for summary in sessions.iter().take(5) {
74//!     let handle = client.sessions().get(&summary.id)?;
75//!     let session = handle.assemble()?;
76//!     println!("{}: {} turns, {} tokens",
77//!         summary.id,
78//!         session.turns.len(),
79//!         session.stats.total_tokens);
80//! }
81//! # Ok(())
82//! # }
83//! ```
84//!
85//! ## Real-time Monitoring
86//!
87//! Watch for events as they happen:
88//!
89//! ```no_run
90//! use agtrace_sdk::Client;
91//! use futures::stream::StreamExt;
92//!
93//! # #[tokio::main]
94//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
95//! let client = Client::connect_default().await?;
96//! let mut stream = client.watch().all_providers().start()?;
97//! while let Some(event) = stream.next().await {
98//!     println!("Event: {:?}", event);
99//! }
100//! # Ok(())
101//! # }
102//! ```
103//!
104//! ## Diagnostics
105//!
106//! Run diagnostic checks on sessions:
107//!
108//! ```no_run
109//! use agtrace_sdk::{Client, Diagnostic, types::SessionFilter};
110//!
111//! # #[tokio::main]
112//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
113//! let client = Client::connect_default().await?;
114//! let sessions = client.sessions().list(SessionFilter::all())?;
115//! if let Some(summary) = sessions.first() {
116//!     let handle = client.sessions().get(&summary.id)?;
117//!     let report = handle.analyze()?
118//!         .check(Diagnostic::Failures)
119//!         .check(Diagnostic::Loops)
120//!         .report()?;
121//!
122//!     println!("Health: {}/100", report.score);
123//!     for insight in &report.insights {
124//!         println!("  Turn {}: {}", insight.turn_index + 1, insight.message);
125//!     }
126//! }
127//! # Ok(())
128//! # }
129//! ```
130//!
131//! ## Standalone API (for testing/simulations)
132//!
133//! ```no_run
134//! use agtrace_sdk::{SessionHandle, types::AgentEvent};
135//!
136//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
137//! // When you have raw events without Client (e.g., testing, simulations)
138//! let events: Vec<AgentEvent> = vec![/* ... */];
139//! let handle = SessionHandle::from_events(events);
140//!
141//! let session = handle.assemble()?;
142//! println!("Session has {} turns", session.turns.len());
143//! # Ok(())
144//! # }
145//! ```
146
147pub mod analysis;
148pub mod client;
149pub mod error;
150pub mod mcp;
151pub mod providers;
152pub mod query;
153pub mod types;
154pub mod watch;
155
156// Re-export core domain types for convenience
157pub use agtrace_engine::AgentSession;
158
159// Public facade
160pub use analysis::{AnalysisReport, Diagnostic, Insight, Severity};
161pub use client::{
162    ChildSessionInfo, Client, ClientBuilder, InsightClient, ProjectClient, SessionClient,
163    SessionHandle, SystemClient, WatchClient,
164};
165pub use error::{Error, Result};
166pub use providers::{Providers, ProvidersBuilder};
167pub use types::{
168    AgentEvent, EventPayload, ExportStrategy, SessionFilter, SessionSummary, StreamId, ToolKind,
169};
170pub use watch::{LiveStream, WatchBuilder};
171
172// Query types for MCP and programmatic usage
173pub use query::{EventType, Provider};
174
175// ============================================================================
176// Low-level Utilities (Power User API)
177// ============================================================================
178
179/// Utility functions for building custom observability tools.
180///
181/// These are stateless, pure functions that power the CLI and can be used
182/// by external tool developers to replicate or extend agtrace functionality.
183///
184/// # When to use this module
185///
186/// - Building custom TUIs or dashboards that need event stream processing
187/// - Writing tests that need to compute project hashes
188/// - Implementing custom project detection logic
189///
190/// # Examples
191///
192/// ## Event Processing
193///
194/// ```no_run
195/// use agtrace_sdk::{Client, utils};
196/// use agtrace_sdk::watch::{StreamEvent, WorkspaceEvent};
197/// use futures::stream::StreamExt;
198///
199/// # #[tokio::main]
200/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
201/// let client = Client::connect_default().await?;
202/// let mut stream = client.watch().all_providers().start()?;
203///
204/// let mut count = 0;
205/// while let Some(workspace_event) = stream.next().await {
206///     if let WorkspaceEvent::Stream(StreamEvent::Events { events, .. }) = workspace_event {
207///         for event in events {
208///             let updates = utils::extract_state_updates(&event);
209///             if updates.is_new_turn {
210///                 println!("New turn started!");
211///             }
212///             if let Some(usage) = updates.usage {
213///                 println!("Token usage: {:?}", usage);
214///             }
215///         }
216///     }
217///     count += 1;
218///     if count >= 10 { break; }
219/// }
220/// # Ok(())
221/// # }
222/// ```
223///
224/// ## Project Hash Computation
225///
226/// ```no_run
227/// use agtrace_sdk::utils;
228///
229/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
230/// let project_root = utils::discover_project_root(None)?;
231/// let hash = utils::project_hash_from_root(&project_root.to_string_lossy());
232/// println!("Project hash: {}", hash);
233/// # Ok(())
234/// # }
235/// ```
236pub mod utils {
237    use crate::types::TokenLimits;
238    use agtrace_providers::ProviderModelLimitResolver;
239
240    // Event processing utilities
241    pub use agtrace_engine::extract_state_updates;
242
243    // Project management utilities
244    pub use agtrace_core::{
245        discover_project_root, project_hash_from_root, resolve_effective_project_hash,
246        resolve_workspace_path,
247    };
248
249    /// Create a TokenLimits instance with the default provider resolver.
250    ///
251    /// This is a convenience function for creating TokenLimits without needing
252    /// to manually instantiate the ProviderModelLimitResolver.
253    ///
254    /// # Example
255    ///
256    /// ```no_run
257    /// use agtrace_sdk::utils;
258    ///
259    /// let token_limits = utils::default_token_limits();
260    /// let limit = token_limits.get_limit("claude-3-5-sonnet");
261    /// ```
262    pub fn default_token_limits() -> TokenLimits<ProviderModelLimitResolver> {
263        TokenLimits::new(ProviderModelLimitResolver)
264    }
265
266    // Event filtering utilities
267
268    /// Filter events suitable for display (excludes sidechain/subagent events).
269    ///
270    /// This is the recommended way to filter events for user-facing displays
271    /// like TUI or console output. It removes internal agent communication
272    /// (sidechains) and shows only main stream events.
273    ///
274    /// # Example
275    ///
276    /// ```no_run
277    /// use agtrace_sdk::{Client, utils};
278    /// use agtrace_sdk::watch::{StreamEvent, WorkspaceEvent};
279    /// use futures::stream::StreamExt;
280    ///
281    /// # #[tokio::main]
282    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
283    /// let client = Client::connect_default().await?;
284    /// let mut stream = client.watch().all_providers().start()?;
285    ///
286    /// let mut count = 0;
287    /// while let Some(workspace_event) = stream.next().await {
288    ///     if let WorkspaceEvent::Stream(StreamEvent::Events { events, .. }) = workspace_event {
289    ///         let display_events = utils::filter_display_events(&events);
290    ///         for event in display_events {
291    ///             println!("Event: {:?}", event.payload);
292    ///         }
293    ///     }
294    ///     count += 1;
295    ///     if count >= 10 { break; }
296    /// }
297    /// # Ok(())
298    /// # }
299    /// ```
300    pub fn filter_display_events(
301        events: &[crate::types::AgentEvent],
302    ) -> Vec<crate::types::AgentEvent> {
303        events
304            .iter()
305            .filter(|e| matches!(e.stream_id, crate::types::StreamId::Main))
306            .cloned()
307            .collect()
308    }
309
310    /// Check if an event should be displayed (non-sidechain).
311    ///
312    /// Returns `true` for main stream events, `false` for sidechain/subagent events.
313    ///
314    /// # Example
315    ///
316    /// ```no_run
317    /// use agtrace_sdk::{Client, utils};
318    /// use agtrace_sdk::watch::{StreamEvent, WorkspaceEvent};
319    /// use futures::stream::StreamExt;
320    ///
321    /// # #[tokio::main]
322    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
323    /// let client = Client::connect_default().await?;
324    /// let mut stream = client.watch().all_providers().start()?;
325    ///
326    /// let mut count = 0;
327    /// while let Some(workspace_event) = stream.next().await {
328    ///     if let WorkspaceEvent::Stream(StreamEvent::Events { events, .. }) = workspace_event {
329    ///         for event in &events {
330    ///             if utils::is_display_event(event) {
331    ///                 println!("Display event: {:?}", event.payload);
332    ///             }
333    ///         }
334    ///     }
335    ///     count += 1;
336    ///     if count >= 10 { break; }
337    /// }
338    /// # Ok(())
339    /// # }
340    /// ```
341    pub fn is_display_event(event: &crate::types::AgentEvent) -> bool {
342        matches!(event.stream_id, crate::types::StreamId::Main)
343    }
344}