vtcode_core/
lib.rs

1//! # vtcode-core - Runtime for VT Code
2//!
3//! `vtcode-core` powers the VT Code terminal coding agent. It provides the
4//! reusable building blocks for multi-provider LLM orchestration, tool
5//! execution, semantic code analysis, and configurable safety policies.
6//!
7//! ## Highlights
8//!
9//! - **Provider Abstraction**: unified LLM interface with adapters for OpenAI,
10//!   Anthropic, xAI, DeepSeek, Gemini, and OpenRouter, including automatic
11//!   failover and spend controls.
12//! - **Prompt Caching**: cross-provider prompt caching system that leverages
13//!   provider-specific caching capabilities (OpenAI's automatic caching, Anthropic's
14//!   cache_control blocks, Gemini's implicit/explicit caching) to reduce costs and
15//!   latency, with configurable settings per provider.
16//! - **Semantic Workspace Model**: incremental tree-sitter parsing for Rust,
17//!   Python, JavaScript, TypeScript, Go, and Java augmented by ast-grep based
18//!   structural search and refactoring.
19//! - **Tool System**: trait-driven registry for shell execution, file IO,
20//!   search, and custom commands, with Tokio-powered concurrency and PTY
21//!   streaming.
22//! - **Configuration-First**: everything is driven by `vtcode.toml`, with
23//!   model, safety, and automation constants centralized in
24//!   `config::constants` and curated metadata in `docs/models.json`.
25//! - **Safety & Observability**: workspace boundary enforcement, command
26//!   allow/deny lists, human-in-the-loop confirmation, and structured event
27//!   logging.
28//!
29//! ## Architecture Overview
30//!
31//! The crate is organized into several key modules:
32//!
33//! - `config/`: configuration loader, defaults, and schema validation.
34//! - `llm/`: provider clients, request shaping, and response handling.
35//! - `tools/`: built-in tool implementations plus registration utilities.
36//! - `context/`: conversation management, summarization, and memory.
37//! - `executor/`: async orchestration for tool invocations and streaming output.
38//! - `tree_sitter/`: language-specific parsers, syntax tree caching, and
39//!   semantic extraction helpers.
40//! - `core/prompt_caching`: cross-provider prompt caching system that leverages
41//!   provider-specific caching mechanisms for cost optimization and reduced latency.
42//!
43//! ## Quickstart
44//!
45//! ```rust,ignore
46//! use vtcode_core::{Agent, VTCodeConfig};
47//!
48//! #[tokio::main]
49//! async fn main() -> Result<(), anyhow::Error> {
50//!     // Load configuration from vtcode.toml or environment overrides
51//!     let config = VTCodeConfig::load()?;
52//!
53//!     // Construct the agent runtime
54//!     let agent = Agent::new(config).await?;
55//!
56//!     // Execute an interactive session
57//!     agent.run().await?;
58//!
59//!     Ok(())
60//! }
61//! ```
62//!
63//! ## Extending VT Code
64//!
65//! Register custom tools or providers by composing the existing traits:
66//!
67//! ```rust,ignore
68//! use vtcode_core::tools::{ToolRegistry, ToolRegistration};
69//!
70//! #[tokio::main]
71//! async fn main() -> Result<(), anyhow::Error> {
72//!     let workspace = std::env::current_dir()?;
73//!     let mut registry = ToolRegistry::new(workspace);
74//!
75//!     let custom_tool = ToolRegistration {
76//!         name: "my_custom_tool".into(),
77//!         description: "A custom tool for specific tasks".into(),
78//!         parameters: serde_json::json!({
79//!             "type": "object",
80//!             "properties": { "input": { "type": "string" } }
81//!         }),
82//!         handler: |_args| async move {
83//!             // Implement your tool behavior here
84//!             Ok(serde_json::json!({ "result": "success" }))
85//!         },
86//!     };
87//!
88//!     registry.register_tool(custom_tool).await?;
89//!     Ok(())
90//! }
91//! ```
92//!
93//! For a complete tour of modules and extension points, read
94//! `docs/ARCHITECTURE.md` and the guides in `docs/project/`.
95//!
96//! ## Agent Client Protocol (ACP)
97//!
98//! VT Code's binary exposes an ACP bridge for Zed. Enable it via the `[acp]` section in
99//! `vtcode.toml`, launch the `vtcode acp` subcommand, and register the binary under
100//! `agent_servers` in Zed's `settings.json`. Detailed instructions and troubleshooting live in the
101//! [Zed ACP integration guide](https://github.com/vinhnx/vtcode/blob/main/docs/guides/zed-acp.md),
102//! with a rendered summary on
103//! [docs.rs](https://docs.rs/vtcode/latest/vtcode/#agent-client-protocol-acp).
104
105//! ### Bridge guarantees
106//!
107//! - Tool exposure follows capability negotiation: `read_file` stays disabled unless Zed
108//!   advertises `fs.read_text_file`.
109//! - Each filesystem request invokes `session/request_permission`, ensuring explicit approval
110//!   within the editor before data flows.
111//! - Cancellation signals propagate into VT Code, cancelling active tool calls and ending the
112//!   turn with `StopReason::Cancelled`.
113//! - ACP `plan` entries track analysis, context gathering, and response drafting for timeline
114//!   parity with Zed.
115//! - Absolute-path checks guard every `read_file` argument before forwarding it to the client.
116//! - Non-tool-capable models trigger reasoning notices and an automatic downgrade to plain
117//!   completions without losing plan consistency.
118
119//! VTCode Core Library
120//!
121//! This crate provides the core functionality for the VTCode agent,
122//! including tool implementations, LLM integration, and utility functions.
123
124// Public modules
125pub mod bash_runner;
126pub mod cli;
127pub mod code;
128pub mod commands;
129pub mod config;
130pub mod constants;
131pub mod core;
132pub mod gemini;
133pub mod llm;
134pub mod markdown_storage;
135pub mod mcp_client;
136pub mod models;
137pub mod project;
138pub mod project_doc;
139pub mod prompts;
140pub mod safety;
141pub mod simple_indexer;
142pub mod tool_policy;
143pub mod tools;
144pub mod types;
145pub mod ui;
146pub mod utils;
147
148// Re-exports for convenience
149pub use bash_runner::BashRunner;
150pub use cli::args::{Cli, Commands};
151pub use code::code_completion::{CompletionEngine, CompletionSuggestion};
152pub use commands::stats::handle_stats_command;
153pub use config::types::{
154    AnalysisDepth, CapabilityLevel, CommandResult, CompressionLevel, ContextConfig, LoggingConfig,
155    OutputFormat, PerformanceMetrics, ReasoningEffortLevel, SessionInfo, ToolConfig,
156};
157pub use config::{
158    AgentClientProtocolConfig, AgentClientProtocolTransport, AgentClientProtocolZedConfig,
159    AgentClientProtocolZedToolsConfig, AgentConfig, VTCodeConfig,
160};
161pub use core::agent::core::Agent;
162pub use core::agent::runner::{
163    AgentRunner, ContextItem as RunnerContextItem, Task as RunnerTask,
164    TaskResults as RunnerTaskResults,
165};
166pub use core::context_compression::{
167    CompressedContext, ContextCompressionConfig, ContextCompressor,
168};
169pub use core::conversation_summarizer::ConversationSummarizer;
170pub use core::performance_profiler::PerformanceProfiler;
171pub use core::prompt_caching::{CacheStats, PromptCache, PromptCacheConfig, PromptOptimizer};
172pub use core::timeout_detector::TimeoutDetector;
173pub use gemini::{Content, FunctionDeclaration, Part};
174pub use llm::{AnyClient, make_client};
175pub use markdown_storage::{MarkdownStorage, ProjectData, ProjectStorage, SimpleKVStorage};
176pub use project::{SimpleCache, SimpleProjectManager};
177pub use prompts::{
178    generate_lightweight_instruction, generate_specialized_instruction, generate_system_instruction,
179};
180pub use simple_indexer::SimpleIndexer;
181pub use tool_policy::{ToolPolicy, ToolPolicyManager};
182pub use tools::advanced_search::{AdvancedSearchTool, SearchOptions};
183pub use tools::grep_search::GrepSearchManager;
184pub use tools::tree_sitter::TreeSitterAnalyzer;
185pub use tools::{
186    ToolRegistration, ToolRegistry, build_function_declarations,
187    build_function_declarations_for_level, build_function_declarations_with_mode,
188};
189pub use ui::diff_renderer::DiffRenderer;
190pub use utils::dot_config::{
191    CacheConfig, DotConfig, DotManager, ProviderConfigs, UiConfig, UserPreferences,
192    WorkspaceTrustLevel, WorkspaceTrustRecord, WorkspaceTrustStore, initialize_dot_folder,
193    load_user_config, save_user_config, update_model_preference, update_theme_preference,
194};
195pub use utils::vtcodegitignore::initialize_vtcode_gitignore;
196
197#[cfg(test)]
198mod tests {
199    use super::*;
200
201    use tempfile::TempDir;
202
203    #[test]
204    fn test_library_exports() {
205        // Test that all public exports are accessible
206        let _cache = PromptCache::new();
207    }
208
209    #[test]
210    fn test_module_structure() {
211        // Test that all modules can be imported
212        // This is a compile-time test that ensures module structure is correct
213    }
214
215    #[test]
216    fn test_version_consistency() {
217        // Test that version information is consistent across modules
218        // This would be more meaningful with actual version checking
219    }
220
221    #[tokio::test]
222    async fn test_tool_registry_integration() {
223        use crate::config::constants::tools;
224
225        let temp_dir = TempDir::new().unwrap();
226        std::env::set_current_dir(&temp_dir).unwrap();
227
228        let mut registry = ToolRegistry::new(temp_dir.path().to_path_buf());
229
230        // Test that we can execute basic tools
231        let list_args = serde_json::json!({
232            "path": "."
233        });
234
235        let result = registry.execute_tool(tools::LIST_FILES, list_args).await;
236        assert!(result.is_ok());
237
238        let response: serde_json::Value = result.unwrap();
239        assert!(response["files"].is_array());
240    }
241
242    #[tokio::test]
243    async fn test_pty_basic_command() {
244        let temp_dir = TempDir::new().unwrap();
245        let workspace = temp_dir.path().to_path_buf();
246        let mut registry = ToolRegistry::new(workspace.clone());
247
248        // Test a simple PTY command
249        let args = serde_json::json!({
250            "command": "echo",
251            "args": ["Hello, PTY!"]
252        });
253
254        let result = registry.execute_tool("run_pty_cmd", args).await;
255        assert!(result.is_ok());
256        let response: serde_json::Value = result.unwrap();
257        assert_eq!(response["success"], true);
258        assert_eq!(response["code"], 0);
259        assert!(response["output"].as_str().unwrap().contains("Hello, PTY!"));
260    }
261
262    #[tokio::test]
263    async fn test_pty_session_management() {
264        let temp_dir = TempDir::new().unwrap();
265        let workspace = temp_dir.path().to_path_buf();
266        let mut registry = ToolRegistry::new(workspace.clone());
267
268        // Test creating a PTY session
269        let args = serde_json::json!({
270            "session_id": "test_session",
271            "command": "bash"
272        });
273
274        let result = registry.execute_tool("create_pty_session", args).await;
275        assert!(result.is_ok());
276        let response: serde_json::Value = result.unwrap();
277        assert_eq!(response["success"], true);
278        assert_eq!(response["session_id"], "test_session");
279
280        // Test listing PTY sessions
281        let args = serde_json::json!({});
282        let result = registry.execute_tool("list_pty_sessions", args).await;
283        assert!(result.is_ok());
284        let response: serde_json::Value = result.unwrap();
285        assert!(
286            response["sessions"]
287                .as_array()
288                .unwrap()
289                .contains(&"test_session".into())
290        );
291
292        // Test closing a PTY session
293        let args = serde_json::json!({
294            "session_id": "test_session"
295        });
296
297        let result = registry.execute_tool("close_pty_session", args).await;
298        assert!(result.is_ok());
299        let response: serde_json::Value = result.unwrap();
300        assert_eq!(response["success"], true);
301        assert_eq!(response["session_id"], "test_session");
302    }
303}