Skip to main content

claude_agent/
lib.rs

1//! # claude-agent-rs
2//!
3//! A lightweight AI coding agent in Rust that runs Claude on your Max subscription for $0.
4//!
5//! ## Quick Start
6//!
7//! ```rust,no_run
8//! use claude_agent::{Agent, AgentConfig};
9//!
10//! #[tokio::main]
11//! async fn main() -> anyhow::Result<()> {
12//!     let agent = Agent::new(AgentConfig::default()).await?;
13//!     let response = agent.chat("what is 2+2?").await?;
14//!     println!("{}", response.text());
15//!     Ok(())
16//! }
17//! ```
18//!
19//! ## Two Inference Backends
20//!
21//! **CLI mode (default)** — pipes through `claude -p --output-format json`, using
22//! your Claude Max subscription. Zero per-token cost.
23//!
24//! **API mode** — direct POST to `api.anthropic.com/v1/messages`. Set
25//! `ANTHROPIC_API_KEY` and `AGENT_BACKEND=api`.
26//!
27//! ## Features
28//!
29//! - **Zero API cost** — calls `claude -p` using your Max subscription
30//! - **8 built-in tools** — bash, read, write, edit, glob, grep, web_fetch, agent
31//! - **SSE streaming** — real-time token output for both API and CLI backends
32//! - **MCP integration** — full JSON-RPC 2.0 client, connects to all your MCP servers
33//! - **Session persistence** — SQLite-backed save/restore with `--resume`
34//! - **Multi-model routing** — auto-route Opus (complex) vs Haiku (simple) with `--auto-route`
35//! - **Sub-agents** — spawn parallel `claude -p` processes with optional git worktree isolation
36//! - **Hooks & skills** — lifecycle hooks and /command dispatch
37//! - **4.5MB binary** — single static binary with SQLite bundled
38//!
39//! ## Architecture
40//!
41//! The agent is organized into 11 layers:
42//!
43//! | Layer | Name | Purpose |
44//! |-------|------|---------|
45//! | 1 | Crown | Skill registry + /command dispatch |
46//! | 2 | Skull | Lifecycle hooks (pre/post tool use) |
47//! | 3 | Security Cage | Permission-gated tool access |
48//! | 4 | Brain Core | Dual-backend inference (CLI + API) |
49//! | 5 | Frontal Lobe | Context engine + CLAUDE.md injection |
50//! | 6 | Temporal Lobe | Sub-agent spawner with worktree isolation |
51//! | 7 | Auth Gate | Authentication routing |
52//! | 8 | Brainstem | 8 built-in tools |
53//! | 9 | Root System | MCP server registry (JSON-RPC 2.0) |
54//! | 10 | Pedestal | HTTP client (reqwest + rustls) |
55//! | 11 | Workbench | Feature flags + session metrics |
56//!
57//! ## Using as a Library
58//!
59//! ```rust,no_run
60//! use claude_agent::{InferenceEngine, InferenceBackend, Message, Role, MessageContent};
61//!
62//! #[tokio::main]
63//! async fn main() -> anyhow::Result<()> {
64//!     let engine = InferenceEngine::new(Some("sk-ant-..."), InferenceBackend::Api);
65//!     let messages = vec![Message {
66//!         role: Role::User,
67//!         content: MessageContent::Text("Hello!".into()),
68//!     }];
69//!     let request = engine.build_request(&messages, None, &[], None);
70//!
71//!     // Tokens stream to the callback as they arrive
72//!     let response = engine.chat_stream(&request, &mut |text| {
73//!         print!("{}", text);
74//!     }).await?;
75//!
76//!     println!("\nTokens used: {}", response.usage.output_tokens);
77//!     Ok(())
78//! }
79//! ```
80
81pub mod types;
82pub mod auth;
83pub mod inference;
84pub mod context;
85pub mod permissions;
86pub mod hooks;
87pub mod skills;
88pub mod flags;
89pub mod memory;
90pub mod telemetry;
91pub mod tools;
92pub mod mcp;
93pub mod sessions;
94pub mod agents;
95pub mod task_queue;
96pub mod plugins;
97
98// Re-export core types for ergonomic use
99pub use types::{
100    Message, Role, MessageContent, ContentBlock,
101    InferenceRequest, InferenceResponse, Usage,
102    ToolDefinition, ToolCall, ToolResult,
103    HookEvent, HookResult,
104    FeatureFlags,
105    AgentStats, AgentConfig,
106};
107pub use auth::AuthGate;
108pub use inference::{InferenceEngine, InferenceBackend};
109pub use context::ContextEngine;
110pub use permissions::PermissionEngine;
111pub use hooks::HookSystem;
112pub use skills::SkillRegistry;
113pub use tools::{Tool, ToolRegistry};
114pub use mcp::McpRegistry;
115
116/// High-level agent interface for library consumers.
117///
118/// Wraps all 11 layers into a single entry point. Create with [`Agent::new`],
119/// then call [`Agent::chat`] to send messages.
120pub struct Agent {
121    pub engine: InferenceEngine,
122    pub context: ContextEngine,
123    pub tools: ToolRegistry,
124    pub hooks: HookSystem,
125    pub skills: SkillRegistry,
126    pub permissions: PermissionEngine,
127    pub mcp: McpRegistry,
128    pub stats: AgentStats,
129    pub cwd: String,
130}
131
132/// Response from a single agent turn.
133pub struct AgentResponse {
134    /// Content blocks returned by the model.
135    pub content: Vec<ContentBlock>,
136    /// Token usage for this turn.
137    pub usage: Usage,
138    /// Model that generated the response.
139    pub model: String,
140}
141
142impl AgentResponse {
143    /// Extract the text content from the response.
144    pub fn text(&self) -> String {
145        self.content.iter().filter_map(|b| {
146            if let ContentBlock::Text { text } = b { Some(text.as_str()) } else { None }
147        }).collect::<Vec<_>>().join("\n")
148    }
149}
150
151impl Agent {
152    /// Create a new agent with the given configuration.
153    ///
154    /// This authenticates, loads MCP servers, builds the system prompt,
155    /// and initializes all 11 layers.
156    pub async fn new(config: AgentConfig) -> anyhow::Result<Self> {
157        let cwd = std::env::current_dir()?.to_string_lossy().into_owned();
158
159        let mut auth = AuthGate::new();
160        auth.authenticate()?;
161
162        let mut hooks = HookSystem::new();
163        hooks.register_defaults();
164
165        let mut mcp = McpRegistry::new();
166        mcp.load_config(&config.mcp_config_path).await;
167
168        let mut context = ContextEngine::new();
169        context.build_system_prompt(&cwd, &config.memory_path).await;
170
171        let engine = InferenceEngine::new(auth.api_key(), auth.backend());
172
173        Ok(Self {
174            engine,
175            context,
176            tools: ToolRegistry::new(),
177            hooks,
178            skills: SkillRegistry::new(),
179            permissions: PermissionEngine::new(),
180            mcp,
181            stats: telemetry::create_stats(),
182            cwd,
183        })
184    }
185
186    /// Send a message and get a response (single turn, no tool execution).
187    pub async fn chat(&self, prompt: &str) -> anyhow::Result<AgentResponse> {
188        let messages = vec![Message {
189            role: Role::User,
190            content: MessageContent::Text(prompt.to_string()),
191        }];
192
193        let tool_defs = self.tools.definitions();
194        let request = self.engine.build_request(
195            &messages,
196            Some(self.context.system_prompt()),
197            &tool_defs,
198            None,
199        );
200
201        let response = self.engine.chat_stream(&request, &mut |_| {}).await?;
202
203        Ok(AgentResponse {
204            content: response.content,
205            usage: response.usage,
206            model: response.model,
207        })
208    }
209}