Skip to main content

vtcode_core/
lib.rs

1#![cfg_attr(
2    not(test),
3    allow(
4        clippy::map_err_ignore,
5        clippy::cast_sign_loss,
6        clippy::collapsible_if,
7        clippy::large_futures,
8    )
9)]
10// Suppress unreachable in test code (assert macros trigger false positives).
11#![cfg_attr(test, allow(clippy::unreachable))]
12#![recursion_limit = "256"]
13//! # vtcode-core - Runtime for VT Code
14//!
15//! `vtcode-core` powers the VT Code terminal coding agent. It provides the
16//! reusable building blocks for multi-provider LLM orchestration, tool
17//! execution, semantic code analysis, and configurable safety policies.
18//!
19//! ## Highlights
20//!
21//! - **Provider Abstraction**: unified LLM interface with adapters for OpenAI,
22//!   Anthropic, xAI, DeepSeek, Gemini, OpenRouter, and Ollama (local), including automatic
23//!   failover and spend controls.
24//! - **Prompt Caching**: cross-provider prompt caching system that leverages
25//!   provider-specific caching capabilities (OpenAI's automatic caching, Anthropic's
26//!   cache_control blocks, Gemini's implicit/explicit caching) to reduce costs and
27//!   latency, with configurable settings per provider.
28//! - **Semantic Workspace Model**: LLM-native code analysis and navigation
29//!   across all modern programming languages.
30//! - **Bash Shell Safety**: tree-sitter-bash integration for critical command validation
31//!   and security enforcement.
32//! - **Tool System**: trait-driven registry for shell execution, file IO,
33//!   search, and custom commands, with Tokio-powered concurrency and PTY
34//!   streaming.
35//! - **Configuration-First**: everything is driven by `vtcode.toml`, with
36//!   model, safety, and automation constants centralized in
37//!   `config::constants` and curated metadata in `docs/models.json`.
38//! - **Safety & Observability**: workspace boundary enforcement, command
39//!   allow/deny lists, human-in-the-loop confirmation, and structured event
40//!   logging for comprehensive audit trails.
41//!
42//! ## Architecture Overview
43//!
44//! The crate is organized into several key modules:
45//!
46//! - `config/`: configuration loader, defaults, and schema validation.
47//! - `llm/`: provider clients, request shaping, and response handling.
48//! - `tools/`: built-in tool implementations plus registration utilities.
49//! - `context/`: conversation management and memory.
50//! - `executor/`: async orchestration for tool invocations and streaming output.
51//! - `core/prompt_caching`: cross-provider prompt caching system that leverages
52//!   provider-specific caching mechanisms for cost optimization and reduced latency.
53//!
54//! ## Quickstart
55//!
56//! ```rust,ignore
57//! use vtcode_core::{Agent, VTCodeConfig};
58//!
59//! #[tokio::main]
60//! async fn main() -> Result<(), anyhow::Error> {
61//!     // Load configuration from vtcode.toml or environment overrides
62//!     let config = VTCodeConfig::load()?;
63//!
64//!     // Construct the agent runtime
65//!     let agent = Agent::new(config).await?;
66//!
67//!     // Execute an interactive session
68//!     agent.run().await?;
69//!
70//!     Ok(())
71//! }
72//! ```
73//!
74//! ## Extending VT Code
75//!
76//! Register custom tools or providers by composing the existing traits:
77//!
78//! ```rust,ignore
79//! use vtcode_core::tools::{ToolRegistry, ToolRegistration};
80//!
81//! #[tokio::main]
82//! async fn main() -> Result<(), anyhow::Error> {
83//!     let workspace = std::env::current_dir()?;
84//!     let mut registry = ToolRegistry::new(workspace);
85//!
86//!     let custom_tool = ToolRegistration {
87//!         name: "my_custom_tool".into(),
88//!         description: "A custom tool for specific tasks".into(),
89//!         parameters: serde_json::json!({
90//!             "type": "object",
91//!             "properties": { "input": { "type": "string" } }
92//!         }),
93//!         handler: |_args| async move {
94//!             // Implement your tool behavior here
95//!             Ok(serde_json::json!({ "result": "success" }))
96//!         },
97//!     };
98//!
99//!     registry.register_tool(custom_tool).await?;
100//!     Ok(())
101//! }
102//! ```
103//!
104//! For a complete tour of modules and extension points, read
105//! `docs/ARCHITECTURE.md` and the guides in `docs/project/`.
106//!
107//! ## Agent Client Protocol (ACP)
108//!
109//! VT Code's binary exposes an ACP bridge for Zed. Enable it via the `[acp]` section in
110//! `vtcode.toml`, launch the `vtcode acp` subcommand, and register the binary under
111//! `agent_servers` in Zed's `settings.json`. Detailed instructions and troubleshooting live in the
112//! [Zed ACP integration guide](https://github.com/vinhnx/vtcode/blob/main/docs/guides/zed-acp.md),
113//! with a rendered summary on
114//! [docs.rs](https://docs.rs/vtcode/latest/vtcode/#agent-client-protocol-acp).
115
116//! ### Bridge guarantees
117//!
118//! - Tool exposure follows capability negotiation: `read_file` stays disabled unless Zed
119//!   advertises `fs.read_text_file`.
120//! - Each filesystem request invokes `session/request_permission`, ensuring explicit approval
121//!   within the editor before data flows.
122//! - Cancellation signals propagate into VT Code, cancelling active tool calls and ending the
123//!   turn with `StopReason::Cancelled`.
124//! - ACP `plan` entries track analysis, context gathering, and response drafting for timeline
125//!   parity with Zed.
126//! - Absolute-path checks guard every `read_file` argument before forwarding it to the client.
127//! - Non-tool-capable models trigger reasoning notices and an automatic downgrade to plain
128//!   completions without losing plan consistency.
129
130//!
131//! VT Code Core Library
132//!
133//! This crate provides the core functionality for the VT Code agent,
134//! including tool implementations, LLM integration, and utility functions.
135
136// Public modules
137pub mod a2a; // Agent2Agent Protocol support
138pub mod acp;
139#[cfg(feature = "anthropic-api")]
140pub mod anthropic_api; // Compatibility facade; canonical implementation lives under llm/providers/anthropic
141pub mod audit;
142pub mod auth; // OAuth PKCE authentication for providers
143pub mod cache; // Unified caching system
144pub mod cli;
145pub mod code;
146pub mod command_safety; // Command safety detection (Codex patterns)
147pub mod commands;
148pub mod compaction;
149pub mod components; // Context-Generic Programming (CGP) substrate for composable tool runtimes
150pub mod config;
151pub mod constants;
152pub mod context; // Vibe coding support: entity resolution, workspace state, conversation memory
153pub mod copilot;
154pub mod core;
155pub mod diagnostics;
156pub mod dotfile_protection; // Comprehensive dotfile protection system
157pub mod error; // Structured error handling
158pub mod exec;
159pub mod exec_policy; // Codex-style execution policy management
160/// Backward-compatible alias: command-level validation now lives in `exec_policy::command_validation`.
161pub use exec_policy::command_validation as execpolicy;
162pub mod gemini; // Compatibility facade; canonical internal import path is llm/providers/gemini::wire
163pub mod git_info; // Git repository information collection
164pub mod hooks;
165pub mod http_client;
166pub mod ide_context;
167pub mod instructions;
168pub mod llm;
169pub mod marketplace;
170pub mod mcp;
171pub mod memory; // Memory monitoring and pressure detection
172pub mod metrics;
173pub mod models;
174pub mod models_manager; // Models discovery, caching, and selection (Codex patterns)
175pub mod notifications;
176pub mod open_responses; // Open Responses specification conformance layer
177pub mod orchestrator;
178pub mod permissions;
179pub mod persistent_memory;
180pub mod plugins;
181pub mod pods;
182pub mod primary_agent;
183pub mod project_doc;
184pub mod prompts;
185pub mod retry;
186mod retry_after;
187pub mod review;
188pub mod safety;
189pub mod sandboxing; // Codex-style sandbox policy and execution environment
190pub mod scheduler;
191pub mod security;
192pub mod session;
193pub mod shutdown;
194pub mod skills;
195pub mod subagents;
196pub mod telemetry;
197pub mod terminal_setup;
198pub mod tool_policy;
199pub mod tools;
200pub mod trace; // Agent Trace specification for AI code attribution
201pub mod turn_metadata; // Turn metadata for LLM requests (git context)
202pub mod types;
203pub mod ui;
204pub mod utils;
205mod zsh_exec_bridge;
206
207// Re-export common error macros and constants
208pub use vtcode_commons::errors::*;
209pub use vtcode_commons::{ctx_err, file_err};
210
211// New MCP enhancement modules
212// Re-exports for convenience
213pub use cli::args::{Cli, Commands};
214pub use code::code_completion::{CompletionEngine, CompletionSuggestion};
215pub use commands::stats::handle_stats_command;
216pub use config::types::{
217    AnalysisDepth, CapabilityLevel, CommandResult, ContextConfig, LoggingConfig, OutputFormat,
218    PerformanceMetrics, ReasoningEffortLevel, SessionInfo, ToolConfig,
219};
220pub use config::{
221    AgentClientProtocolConfig, AgentClientProtocolTransport, AgentClientProtocolZedConfig,
222    AgentClientProtocolZedToolsConfig, AgentConfig, IdeContextConfig, IdeContextProviderConfig,
223    IdeContextProviderFamily, IdeContextProviderMode, IdeContextProvidersConfig,
224    PluginRuntimeConfig, PluginTrustLevel, VTCodeConfig, WorkspaceTrustLevel,
225};
226pub use core::agent::core::Agent;
227pub use core::agent::runner::AgentRunner;
228pub use core::agent::task::{
229    ContextItem as RunnerContextItem, Task as RunnerTask, TaskOutcome as RunnerTaskOutcome,
230    TaskResults as RunnerTaskResults,
231};
232pub use core::memory_pool::{MemoryPool, global_pool};
233pub use core::performance_profiler::{BenchmarkResults, BenchmarkUtils, PerformanceProfiler};
234pub use core::threads::{
235    SubmissionId, ThreadBootstrap, ThreadEventRecord, ThreadId, ThreadManager, ThreadRuntimeHandle,
236    ThreadSnapshot, build_thread_archive_metadata, loaded_skills_from_session_listing,
237    messages_from_session_listing,
238};
239pub use ide_context::{
240    EditorContextSnapshot, EditorFileContext, EditorLineRange, EditorSelectionContext,
241    EditorSelectionRange,
242};
243pub use primary_agent::{
244    ActivePrimaryAgent, ActivePrimaryAgentSpecIdentity, ActivePrimaryAgentState,
245    PrimaryAgentResolutionError, apply_primary_agent_tool_policy, clamp_primary_permission_mode,
246    resolve_discovered_primary_agent, resolve_primary_agent,
247};
248pub use subagents::{
249    SendInputRequest as SubagentSendInputRequest, SpawnAgentRequest as SubagentSpawnRequest,
250    SpawnBackgroundSubprocessRequest as SubagentSpawnBackgroundSubprocessRequest,
251    SubagentController, SubagentControllerConfig, SubagentInputItem, SubagentStatus,
252    SubagentStatusEntry,
253};
254pub use vtcode_bash_runner::BashRunner;
255
256pub use core::prompt_caching::{CacheStats, PromptCache, PromptCacheConfig, PromptOptimizer};
257pub use core::timeout_detector::TimeoutDetector;
258pub use diagnostics::{
259    DiagnosticReport, HealthSample, LabeledAction, PredictiveMonitor, RecoveryAction,
260    RecoveryPlaybook,
261};
262pub use dotfile_protection::{
263    AccessType as DotfileAccessType, AuditEntry as DotfileAuditEntry, AuditLog as DotfileAuditLog,
264    AuditOutcome as DotfileAuditOutcome, BackupManager as DotfileBackupManager, DotfileBackup,
265    DotfileGuardian, ProtectionDecision, ProtectionViolation, get_global_guardian,
266    init_global_guardian, is_protected_dotfile,
267};
268pub use error::{
269    ErrorCategory as VtCodeErrorCategory, ErrorCode as VtCodeErrorCode, Result as VtCodeResult,
270    VtCodeError,
271};
272pub use exec::events::{
273    AgentMessageItem, CommandExecutionItem, CommandExecutionStatus, EVENT_SCHEMA_VERSION,
274    ErrorItem, FileChangeItem, FileUpdateChange, ItemCompletedEvent, ItemStartedEvent,
275    ItemUpdatedEvent, McpToolCallItem, McpToolCallStatus, PatchApplyStatus, PatchChangeKind,
276    PlanDeltaEvent, PlanItem, ReasoningItem, ThreadEvent, ThreadItem, ThreadItemDetails,
277    ThreadStartedEvent, ToolCallStatus, ToolInvocationItem, ToolOutputItem, TurnCompletedEvent,
278    TurnFailedEvent, TurnStartedEvent, Usage, VersionedThreadEvent, WebSearchItem,
279};
280pub use exec::{CodeExecutor, ExecutionConfig, ExecutionResult, Language};
281pub use llm::providers::gemini::wire::{Content, FunctionDeclaration, Part};
282pub use llm::{AnyClient, make_client};
283pub use mcp::{
284    tool_discovery::{DetailLevel, ToolDiscovery, ToolDiscoveryResult},
285    validate_mcp_config,
286};
287pub use memory::{MemoryCheckpoint, MemoryMonitor, MemoryPressure, MemoryReport};
288pub use models_manager::{
289    ModelFamily, ModelPreset, ModelsCache, ModelsManager, builtin_model_presets,
290    model_family::find_family_for_model,
291};
292pub use notifications::{
293    NotificationConfig, NotificationEvent, NotificationManager, apply_global_notification_config,
294    apply_global_notification_config_from_vtcode, get_global_notification_manager,
295    init_global_notification_manager, init_global_notification_manager_with_config,
296    notify_command_failure, notify_error, notify_human_in_the_loop, notify_tool_failure,
297    notify_tool_success, send_global_notification,
298};
299pub use orchestrator::{
300    DistributedOrchestrator, ExecutionTarget, ExecutorRegistry, LocalExecutor, ScheduledWork,
301    WorkExecutor,
302};
303pub use pods::*;
304pub use prompts::{
305    generate_lightweight_instruction, generate_specialized_instruction, generate_system_instruction,
306};
307pub use retry::{RetryDecision, RetryPolicy};
308pub use security::{IntegrityTag, PayloadEnvelope, ZeroTrustContext};
309pub use telemetry::{TelemetryEvent, TelemetryPipeline};
310pub use zsh_exec_bridge::maybe_run_zsh_exec_wrapper_mode;
311
312// Open Responses specification types
313pub use open_responses::{
314    ContentPart, CustomItem, DualEventEmitter, FunctionCallItem, FunctionCallOutputItem,
315    IncompleteDetails, IncompleteReason, InputTokensDetails, ItemStatus, MessageItem, MessageRole,
316    OpenResponseError, OpenResponseErrorCode, OpenResponseErrorType, OpenResponsesCallback,
317    OpenResponsesIntegration, OpenResponsesProvider, OpenUsage, OutputItem, OutputItemId,
318    OutputTokensDetails, ReasoningItem as OpenReasoningItem, Response as OpenResponse,
319    ResponseBuilder, ResponseId, ResponseStatus, ResponseStreamEvent, StreamEventEmitter,
320    ToOpenResponse, VecStreamEmitter, generate_item_id, generate_response_id,
321};
322
323pub use tool_policy::{ToolPolicy, ToolPolicyManager};
324
325// Codex-style execution policy and sandboxing
326pub use exec_policy::{
327    AskForApproval, Decision, ExecApprovalRequirement, ExecPolicyAmendment, ExecPolicyConfig,
328    ExecPolicyManager, Policy, PolicyEvaluation, PolicyParser, PrefixRule, RuleMatch,
329    SharedExecPolicyManager,
330};
331pub use sandboxing::{
332    CommandSpec as SandboxCommandSpec, ExecEnv as SandboxExecEnv, ExecExpiration,
333    SandboxManager as CodexSandboxManager, SandboxPermissions as CodexSandboxPermissions,
334    SandboxPolicy as CodexSandboxPolicy, SandboxType, WritableRoot,
335};
336
337pub use tools::OptimizedToolRegistry;
338pub use tools::grep_file::GrepSearchManager;
339pub use tools::{ToolRegistration, ToolRegistry};
340pub use ui::diff_renderer::DiffRenderer;
341pub use utils::dot_config::{
342    CacheConfig, DotConfig, DotManager, ProviderConfigs, UiConfig, UserPreferences,
343    WorkspaceTrustRecord, WorkspaceTrustStore, initialize_dot_folder, load_user_config,
344    load_workspace_trust_level, save_user_config, update_model_preference, update_theme_preference,
345    update_workspace_trust,
346};
347pub use utils::vtcodegitignore::initialize_vtcode_gitignore;
348pub use vtcode_indexer::SimpleIndexer;
349pub use vtcode_markdown_store::{
350    MarkdownStorage, ProjectData, ProjectStorage, SimpleCache, SimpleKVStorage,
351    SimpleProjectManager,
352};
353
354#[cfg(test)]
355mod memory_tests;
356
357#[cfg(test)]
358mod memory_integration_tests;
359
360#[cfg(test)]
361mod config_verification_tests;
362
363#[cfg(test)]
364mod tests {
365    use super::*;
366
367    use tempfile::TempDir;
368
369    struct CwdGuard {
370        previous: std::path::PathBuf,
371    }
372
373    impl CwdGuard {
374        fn new() -> Self {
375            let previous = std::env::current_dir().expect("current dir");
376            Self { previous }
377        }
378    }
379
380    impl Drop for CwdGuard {
381        fn drop(&mut self) {
382            let _ = std::env::set_current_dir(&self.previous);
383        }
384    }
385
386    #[tokio::test]
387    async fn test_library_exports() {
388        // Test that all public exports are accessible
389        let _cache = PromptCache::new().await;
390    }
391
392    #[test]
393    fn test_module_structure() {
394        // Test that all modules can be imported
395        // This is a compile-time test that ensures module structure is correct
396    }
397
398    #[test]
399    fn test_version_consistency() {
400        // Test that version information is consistent across modules
401        // This would be more meaningful with actual version checking
402    }
403
404    #[tokio::test]
405    async fn test_tool_registry_integration() {
406        use crate::config::constants::tools;
407
408        let temp_dir = TempDir::new().expect("Failed to create temp dir");
409        let _cwd_guard = CwdGuard::new();
410        std::env::set_current_dir(&temp_dir).expect("Failed to change dir");
411
412        let registry = ToolRegistry::new(temp_dir.path().to_path_buf()).await;
413        registry
414            .initialize_async()
415            .await
416            .expect("Failed to init registry");
417
418        // Test that we can execute basic tools
419        let list_args = serde_json::json!({
420            "action": "list",
421            "path": "."
422        });
423
424        let result = registry
425            .execute_tool(tools::UNIFIED_SEARCH, list_args)
426            .await;
427        assert!(result.is_ok());
428
429        let response: serde_json::Value = result.expect("Failed to execute unified_search:list");
430        assert!(response.is_object() || response.is_array());
431    }
432
433    #[tokio::test]
434    async fn test_pty_basic_command() {
435        let temp_dir = TempDir::new().expect("Failed to create temp dir");
436        let workspace = temp_dir.path().to_path_buf();
437        let registry = ToolRegistry::new(workspace.clone()).await;
438        registry
439            .initialize_async()
440            .await
441            .expect("Failed to init registry");
442
443        // Test a simple PTY command
444        let args = serde_json::json!({
445            "command": "echo",
446            "args": ["Hello, PTY!"]
447        });
448
449        let result = registry.execute_tool("run_pty_cmd", args).await;
450        assert!(result.is_ok());
451        let response: serde_json::Value = result.expect("Failed to run PTY");
452        assert_eq!(response["is_exited"], true);
453        assert_eq!(response["exit_code"], 0);
454        assert!(response["output"].is_string());
455    }
456
457    #[tokio::test]
458    async fn test_pty_session_management() {
459        let temp_dir = TempDir::new().expect("Failed to create temp dir");
460        let workspace = temp_dir.path().to_path_buf();
461        let registry = ToolRegistry::new(workspace.clone()).await;
462        registry
463            .initialize_async()
464            .await
465            .expect("Failed to init registry");
466
467        // Test creating a PTY session
468        let args = serde_json::json!({
469            "command": "cat",
470            "yield_time_ms": 10
471        });
472
473        let result = registry.execute_tool("create_pty_session", args).await;
474        assert!(result.is_ok());
475        let response: serde_json::Value = result.expect("Failed to create PTY session");
476        assert_eq!(response["success"], true);
477        assert_eq!(response["is_exited"], false);
478        let session_id = response["session_id"]
479            .as_str()
480            .expect("create_pty_session should return a session id")
481            .to_string();
482        assert!(!session_id.is_empty());
483
484        // Test listing PTY sessions
485        let args = serde_json::json!({});
486        let result = registry.execute_tool("list_pty_sessions", args).await;
487        assert!(result.is_ok());
488        let response: serde_json::Value = result.expect("Failed to list PTY sessions");
489        assert!(response.is_object() || response.is_array());
490
491        // Test closing a PTY session
492        let args = serde_json::json!({
493            "session_id": session_id.clone()
494        });
495
496        let result = registry.execute_tool("close_pty_session", args).await;
497        assert!(result.is_ok());
498        let response: serde_json::Value = result.expect("Failed to close PTY session");
499        assert_eq!(response["success"], true);
500        assert_eq!(response["session_id"], session_id);
501    }
502}