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}