1#![cfg_attr(docsrs, feature(doc_cfg))]
53#![allow(missing_docs)]
54#![deny(rustdoc::broken_intra_doc_links)]
55
56pub mod agent;
57pub mod auth;
58pub mod budget;
59pub mod client;
60pub mod common;
61pub mod config;
62pub mod context;
63pub mod hooks;
64pub mod mcp;
65pub mod observability;
66pub mod output_style;
67pub mod permissions;
68pub mod prelude;
69pub mod prompts;
70pub mod security;
71pub mod session;
72pub mod skills;
73pub mod subagents;
74pub mod tools;
75pub mod types;
76
77pub use agent::{
79 Agent, AgentBuilder, AgentConfig, AgentEvent, AgentMetrics, AgentModelConfig, AgentResult,
80 AgentState, BudgetConfig, DEFAULT_COMPACT_KEEP_MESSAGES, ExecutionConfig, PromptConfig,
81 SecurityConfig, SystemPromptMode, ToolStats,
82};
83#[cfg(feature = "cli-integration")]
84pub use auth::ClaudeCliProvider;
85pub use auth::{
86 ApiKeyHelper, Auth, AwsCredentialRefresh, AwsCredentials, ChainProvider, Credential,
87 CredentialManager, CredentialProvider, EnvironmentProvider, ExplicitProvider, OAuthConfig,
88 OAuthConfigBuilder,
89};
90pub use budget::{
91 BudgetStatus, BudgetTracker, ModelPricing, OnExceed, PricingTable, PricingTableBuilder,
92 TenantBudget, TenantBudgetManager,
93};
94pub use client::{
95 AnthropicAdapter, BetaConfig, BetaFeature, CircuitBreaker, CircuitConfig, CircuitState, Client,
96 ClientBuilder, ClientCertConfig, CloudProvider, CountTokensRequest, CountTokensResponse,
97 EffortLevel, ExponentialBackoff, FallbackConfig, FallbackTrigger, File, FileData, FileDownload,
98 FileListResponse, FilesClient, GatewayConfig, ModelConfig, ModelType, NetworkConfig,
99 OutputConfig, ProviderAdapter, ProviderConfig, ProxyConfig, Resilience, ResilienceConfig,
100 RetryConfig, UploadFileRequest, strict_schema, transform_for_strict,
101};
102pub use common::{Named, Provider, SourceType, ToolRestricted};
103pub use context::{
104 ContextBuilder, FileMemoryProvider, InMemoryProvider, LeveledMemoryProvider, MemoryContent,
105 MemoryLevel, MemoryLoader, MemoryProvider, PromptOrchestrator, RoutingStrategy, RuleIndex,
106 SkillIndex, StaticContext,
107};
108pub use hooks::{CommandHook, Hook, HookContext, HookEvent, HookInput, HookManager, HookOutput};
109pub use observability::{
110 AgentMetrics as ObservabilityMetrics, MetricsConfig, MetricsRegistry, ObservabilityConfig,
111 SpanContext, TracingConfig,
112};
113pub use output_style::{
114 OutputStyle, OutputStyleSourceType, builtin_styles, default_style, explanatory_style,
115 learning_style,
116};
117#[cfg(feature = "cli-integration")]
118pub use output_style::{OutputStyleLoader, SystemPromptGenerator};
119pub use permissions::{PermissionDecision, PermissionMode, PermissionPolicy, PermissionResult};
120pub use session::{
121 CompactExecutor, CompactStrategy, Session, SessionConfig, SessionId, SessionManager,
122};
123#[cfg(feature = "cli-integration")]
124pub use skills::FileSkillProvider;
125pub use skills::{
126 ChainSkillProvider, CommandLoader, InMemorySkillProvider, SkillDefinition, SkillExecutor,
127 SkillProviderTrait, SkillRegistry, SkillResult, SkillTool, SlashCommand,
128};
129pub use subagents::{
130 ChainSubagentProvider, InMemorySubagentProvider, SubagentDefinition, SubagentProviderTrait,
131 SubagentRegistry, builtin_subagents, find_builtin,
132};
133#[cfg(feature = "cli-integration")]
134pub use subagents::{FileSubagentProvider, SubagentLoader};
135pub use tools::{
136 ExecutionContext, SchemaTool, Tool, ToolAccess, ToolRegistry, ToolRegistryBuilder,
137};
138pub use types::{
139 CompactResult, ContentBlock, DocumentBlock, ImageSource, Message, Role, ToolError, ToolOutput,
140 UserLocation, WebSearchTool,
141};
142
143pub use mcp::{
145 McpContent, McpError, McpManager, McpResourceDefinition, McpResult, McpServerConfig,
146 McpServerInfo, McpServerState, McpToolDefinition, McpToolResult, ReconnectPolicy,
147};
148
149pub use security::{SecurityContext, SecurityContextBuilder};
151
152#[cfg(feature = "aws")]
153pub use client::BedrockAdapter;
154#[cfg(feature = "azure")]
155pub use client::FoundryAdapter;
156#[cfg(feature = "gcp")]
157pub use client::VertexAdapter;
158
159#[derive(Debug, thiserror::Error)]
161#[non_exhaustive]
162pub enum Error {
163 #[error("API error ({status:?}): {message}")]
164 Api {
165 message: String,
166 status: Option<u16>,
167 error_type: Option<String>,
168 },
169
170 #[error("Authentication error: {message}")]
171 Auth { message: String },
172
173 #[error("Network error: {0}")]
174 Network(#[from] reqwest::Error),
175
176 #[error("JSON error: {0}")]
177 Json(#[from] serde_json::Error),
178
179 #[error("Parse error: {0}")]
180 Parse(String),
181
182 #[error("Tool error: {0}")]
183 Tool(#[from] types::ToolError),
184
185 #[error("Configuration error: {0}")]
186 Config(String),
187
188 #[error("IO error: {0}")]
189 Io(#[from] std::io::Error),
190
191 #[error("Rate limit exceeded, retry after {retry_after:?}")]
192 RateLimit {
193 retry_after: Option<std::time::Duration>,
194 },
195
196 #[error("Context window exceeded: {current} / {max} tokens")]
197 ContextOverflow { current: usize, max: usize },
198
199 #[error("Execution timed out after {0:?}")]
200 Timeout(std::time::Duration),
201
202 #[error("Invalid request: {0}")]
203 InvalidRequest(String),
204
205 #[error("Stream error: {0}")]
206 Stream(String),
207
208 #[error("Environment variable error: {0}")]
209 Env(#[from] std::env::VarError),
210
211 #[error("Operation '{operation}' not supported by provider '{provider}'")]
212 NotSupported {
213 provider: &'static str,
214 operation: &'static str,
215 },
216
217 #[error("Permission denied: {0}")]
218 Permission(String),
219
220 #[error("Budget exceeded: used ${used:.2} of ${limit:.2} limit")]
221 BudgetExceeded { used: f64, limit: f64 },
222
223 #[error("Model overloaded: {model}")]
224 ModelOverloaded { model: String },
225
226 #[error("Session error: {0}")]
227 Session(String),
228
229 #[error("MCP error: {0}")]
230 Mcp(String),
231}
232
233impl Error {
234 pub fn auth(message: impl Into<String>) -> Self {
235 Error::Auth {
236 message: message.into(),
237 }
238 }
239
240 pub fn is_retryable(&self) -> bool {
241 matches!(
242 self,
243 Error::RateLimit { .. }
244 | Error::Network(_)
245 | Error::Api {
246 status: Some(500..=599),
247 ..
248 }
249 )
250 }
251
252 pub fn is_unauthorized(&self) -> bool {
253 matches!(
254 self,
255 Error::Api {
256 status: Some(401),
257 ..
258 } | Error::Auth { .. }
259 )
260 }
261
262 pub fn is_overloaded(&self) -> bool {
263 match self {
264 Error::Api {
265 status: Some(529 | 503),
266 ..
267 } => true,
268 Error::Api {
269 error_type: Some(t),
270 ..
271 } if t.contains("overloaded") => true,
272 Error::Api { message, .. } if message.to_lowercase().contains("overloaded") => true,
273 Error::ModelOverloaded { .. } => true,
274 _ => false,
275 }
276 }
277
278 pub fn status_code(&self) -> Option<u16> {
279 match self {
280 Error::Api { status, .. } => *status,
281 _ => None,
282 }
283 }
284
285 pub fn retry_after(&self) -> Option<std::time::Duration> {
286 match self {
287 Error::RateLimit { retry_after } => *retry_after,
288 _ => None,
289 }
290 }
291}
292
293impl From<config::ConfigError> for Error {
294 fn from(err: config::ConfigError) -> Self {
295 match err {
296 config::ConfigError::NotFound { key } => {
297 Error::Config(format!("Key not found: {}", key))
298 }
299 config::ConfigError::InvalidValue { key, message } => {
300 Error::Config(format!("Invalid value for {}: {}", key, message))
301 }
302 config::ConfigError::Serialization(e) => Error::Json(e),
303 config::ConfigError::Io(e) => Error::Io(e),
304 config::ConfigError::Env(e) => Error::Env(e),
305 config::ConfigError::Provider { message } => Error::Config(message),
306 config::ConfigError::ValidationErrors(errors) => Error::Config(errors.to_string()),
307 }
308 }
309}
310
311impl From<context::ContextError> for Error {
312 fn from(err: context::ContextError) -> Self {
313 match err {
314 context::ContextError::Source { message } => Error::Config(message),
315 context::ContextError::TokenBudgetExceeded { current, limit } => {
316 Error::ContextOverflow {
317 current: current as usize,
318 max: limit as usize,
319 }
320 }
321 context::ContextError::SkillNotFound { name } => {
322 Error::Config(format!("Skill not found: {}", name))
323 }
324 context::ContextError::RuleNotFound { name } => {
325 Error::Config(format!("Rule not found: {}", name))
326 }
327 context::ContextError::Parse { message } => Error::Parse(message),
328 context::ContextError::Io(e) => Error::Io(e),
329 }
330 }
331}
332
333impl From<session::SessionError> for Error {
334 fn from(err: session::SessionError) -> Self {
335 match err {
336 session::SessionError::NotFound { id } => {
337 Error::Config(format!("Session not found: {}", id))
338 }
339 session::SessionError::Expired { id } => {
340 Error::Config(format!("Session expired: {}", id))
341 }
342 session::SessionError::PermissionDenied { reason } => Error::auth(reason),
343 session::SessionError::Storage { message } => Error::Config(message),
344 session::SessionError::PersistenceError(msg) => Error::Config(msg),
345 session::SessionError::Serialization(e) => Error::Json(e),
346 session::SessionError::Compact { message } => Error::Config(message),
347 session::SessionError::Context(e) => e.into(),
348 session::SessionError::Plan { message } => Error::Config(message),
349 }
350 }
351}
352
353impl From<security::SecurityError> for Error {
354 fn from(err: security::SecurityError) -> Self {
355 match err {
356 security::SecurityError::Io(e) => Error::Io(e),
357 security::SecurityError::BashBlocked(msg) => Error::Permission(msg),
358 security::SecurityError::DeniedPath(path) => {
359 Error::Permission(format!("denied path: {}", path.display()))
360 }
361 security::SecurityError::PathEscape(path) => {
362 Error::Permission(format!("path escapes sandbox: {}", path.display()))
363 }
364 security::SecurityError::NotWithinSandbox(path) => {
365 Error::Permission(format!("path not within sandbox: {}", path.display()))
366 }
367 _ => Error::Permission(err.to_string()),
368 }
369 }
370}
371
372impl From<security::sandbox::SandboxError> for Error {
373 fn from(err: security::sandbox::SandboxError) -> Self {
374 match err {
375 security::sandbox::SandboxError::Io(e) => Error::Io(e),
376 security::sandbox::SandboxError::NotSupported => {
377 Error::Config("sandbox not supported on this platform".into())
378 }
379 security::sandbox::SandboxError::NotAvailable(msg) => {
380 Error::Config(format!("sandbox not available: {}", msg))
381 }
382 _ => Error::Config(err.to_string()),
383 }
384 }
385}
386
387impl From<mcp::McpError> for Error {
388 fn from(err: mcp::McpError) -> Self {
389 match err {
390 mcp::McpError::Io(e) => Error::Io(e),
391 mcp::McpError::Json(e) => Error::Json(e),
392 _ => Error::Mcp(err.to_string()),
393 }
394 }
395}
396
397pub type Result<T> = std::result::Result<T, Error>;
398
399pub async fn query(prompt: &str) -> Result<String> {
401 let client = Client::builder().auth(Auth::FromEnv).await?.build().await?;
402 client.query(prompt).await
403}
404
405pub async fn query_with_model(model: &str, prompt: &str) -> Result<String> {
407 use client::CreateMessageRequest;
408 let client = Client::builder().auth(Auth::FromEnv).await?.build().await?;
409 let request =
410 CreateMessageRequest::new(model, vec![types::Message::user(prompt)]).with_max_tokens(8192);
411 let response = client.send(request).await?;
412 Ok(response.text())
413}
414
415pub async fn stream(
417 prompt: &str,
418) -> Result<impl futures::Stream<Item = Result<String>> + Send + 'static + use<>> {
419 let client = Client::builder().auth(Auth::FromEnv).await?.build().await?;
420 client.stream(prompt).await
421}
422
423#[cfg(test)]
424mod tests {
425 use super::*;
426
427 #[test]
428 fn test_error_display() {
429 let err = Error::Api {
430 message: "Invalid API key".to_string(),
431 status: Some(401),
432 error_type: None,
433 };
434 assert!(err.to_string().contains("Invalid API key"));
435 }
436
437 #[test]
438 fn test_error_is_retryable() {
439 let rate_limit = Error::RateLimit { retry_after: None };
440 assert!(rate_limit.is_retryable());
441
442 let server_error = Error::Api {
443 message: "Internal error".to_string(),
444 status: Some(500),
445 error_type: None,
446 };
447 assert!(server_error.is_retryable());
448
449 let auth_error = Error::auth("Invalid token");
450 assert!(!auth_error.is_retryable());
451 }
452
453 #[test]
454 fn test_config_error_conversion() {
455 let config_err = config::ConfigError::NotFound {
456 key: "api_key".to_string(),
457 };
458 let err: Error = config_err.into();
459 assert!(matches!(err, Error::Config(_)));
460 }
461}