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, active_primary_agent_permissions,
246    apply_primary_agent_prompt_context, apply_primary_agent_tool_policy,
247    build_primary_agent_hook_config, build_primary_agent_runtime_config,
248    evaluate_active_primary_agent_permissions, resolve_discovered_primary_agent,
249    resolve_primary_agent,
250};
251pub use subagents::{
252    SendInputRequest as SubagentSendInputRequest, SpawnAgentRequest as SubagentSpawnRequest,
253    SpawnBackgroundSubprocessRequest as SubagentSpawnBackgroundSubprocessRequest,
254    SubagentController, SubagentControllerConfig, SubagentInputItem, SubagentStatus,
255    SubagentStatusEntry,
256};
257pub use vtcode_bash_runner::BashRunner;
258
259pub use core::prompt_caching::{CacheStats, PromptCache, PromptCacheConfig, PromptOptimizer};
260pub use core::timeout_detector::TimeoutDetector;
261pub use diagnostics::{
262    DiagnosticReport, HealthSample, LabeledAction, PredictiveMonitor, RecoveryAction,
263    RecoveryPlaybook,
264};
265pub use dotfile_protection::{
266    AccessType as DotfileAccessType, AuditEntry as DotfileAuditEntry, AuditLog as DotfileAuditLog,
267    AuditOutcome as DotfileAuditOutcome, BackupManager as DotfileBackupManager, DotfileBackup,
268    DotfileGuardian, ProtectionDecision, ProtectionViolation, get_global_guardian,
269    init_global_guardian, is_protected_dotfile,
270};
271pub use error::{
272    ErrorCategory as VtCodeErrorCategory, ErrorCode as VtCodeErrorCode, Result as VtCodeResult,
273    VtCodeError,
274};
275pub use exec::events::{
276    AgentMessageItem, CommandExecutionItem, CommandExecutionStatus, EVENT_SCHEMA_VERSION,
277    ErrorItem, FileChangeItem, FileUpdateChange, ItemCompletedEvent, ItemStartedEvent,
278    ItemUpdatedEvent, McpToolCallItem, McpToolCallStatus, PatchApplyStatus, PatchChangeKind,
279    PlanDeltaEvent, PlanItem, ReasoningItem, ThreadEvent, ThreadItem, ThreadItemDetails,
280    ThreadStartedEvent, ToolCallStatus, ToolInvocationItem, ToolOutputItem, TurnCompletedEvent,
281    TurnFailedEvent, TurnStartedEvent, Usage, VersionedThreadEvent, WebSearchItem,
282};
283pub use exec::{CodeExecutor, ExecutionConfig, ExecutionResult, Language};
284pub use llm::providers::gemini::wire::{Content, FunctionDeclaration, Part};
285pub use llm::{AnyClient, make_client};
286pub use mcp::{
287    tool_discovery::{DetailLevel, ToolDiscovery, ToolDiscoveryResult},
288    validate_mcp_config,
289};
290pub use memory::{MemoryCheckpoint, MemoryMonitor, MemoryPressure, MemoryReport};
291pub use models_manager::{
292    ModelFamily, ModelPreset, ModelsCache, ModelsManager, builtin_model_presets,
293    model_family::find_family_for_model,
294};
295pub use notifications::{
296    NotificationConfig, NotificationEvent, NotificationManager, apply_global_notification_config,
297    apply_global_notification_config_from_vtcode, get_global_notification_manager,
298    init_global_notification_manager, init_global_notification_manager_with_config,
299    notify_command_failure, notify_error, notify_human_in_the_loop, notify_tool_failure,
300    notify_tool_success, send_global_notification,
301};
302pub use orchestrator::{
303    DistributedOrchestrator, ExecutionTarget, ExecutorRegistry, LocalExecutor, ScheduledWork,
304    WorkExecutor,
305};
306pub use pods::*;
307pub use prompts::{
308    generate_lightweight_instruction, generate_specialized_instruction, generate_system_instruction,
309};
310pub use retry::{RetryDecision, RetryPolicy};
311pub use security::{IntegrityTag, PayloadEnvelope, ZeroTrustContext};
312pub use telemetry::{TelemetryEvent, TelemetryPipeline};
313pub use zsh_exec_bridge::maybe_run_zsh_exec_wrapper_mode;
314
315// Open Responses specification types
316pub use open_responses::{
317    ContentPart, CustomItem, DualEventEmitter, FunctionCallItem, FunctionCallOutputItem,
318    IncompleteDetails, IncompleteReason, InputTokensDetails, ItemStatus, MessageItem, MessageRole,
319    OpenResponseError, OpenResponseErrorCode, OpenResponseErrorType, OpenResponsesCallback,
320    OpenResponsesIntegration, OpenResponsesProvider, OpenUsage, OutputItem, OutputItemId,
321    OutputTokensDetails, ReasoningItem as OpenReasoningItem, Response as OpenResponse,
322    ResponseBuilder, ResponseId, ResponseStatus, ResponseStreamEvent, StreamEventEmitter,
323    ToOpenResponse, VecStreamEmitter, generate_item_id, generate_response_id,
324};
325
326pub use tool_policy::{ToolPolicy, ToolPolicyManager};
327
328// Codex-style execution policy and sandboxing
329pub use exec_policy::{
330    AskForApproval, Decision, ExecApprovalRequirement, ExecPolicyAmendment, ExecPolicyConfig,
331    ExecPolicyManager, Policy, PolicyEvaluation, PolicyParser, PrefixRule, RuleMatch,
332    SharedExecPolicyManager,
333};
334pub use sandboxing::{
335    CommandSpec as SandboxCommandSpec, ExecEnv as SandboxExecEnv, ExecExpiration,
336    SandboxManager as CodexSandboxManager, SandboxPermissions as CodexSandboxPermissions,
337    SandboxPolicy as CodexSandboxPolicy, SandboxType, WritableRoot,
338};
339
340pub use tools::OptimizedToolRegistry;
341pub use tools::grep_file::GrepSearchManager;
342pub use tools::{ToolRegistration, ToolRegistry};
343pub use ui::diff_renderer::DiffRenderer;
344pub use utils::dot_config::{
345    CacheConfig, DotConfig, DotManager, ProviderConfigs, UiConfig, UserPreferences,
346    WorkspaceTrustRecord, WorkspaceTrustStore, initialize_dot_folder, load_user_config,
347    load_workspace_trust_level, save_user_config, update_model_preference, update_theme_preference,
348    update_workspace_trust,
349};
350pub use utils::vtcodegitignore::initialize_vtcode_gitignore;
351pub use vtcode_indexer::SimpleIndexer;
352pub use vtcode_markdown_store::{
353    MarkdownStorage, ProjectData, ProjectStorage, SimpleCache, SimpleKVStorage,
354    SimpleProjectManager,
355};
356
357#[cfg(test)]
358mod memory_tests;
359
360#[cfg(test)]
361mod memory_integration_tests;
362
363#[cfg(test)]
364mod config_verification_tests;
365
366#[cfg(test)]
367mod tests {
368    use super::*;
369
370    use tempfile::TempDir;
371
372    struct CwdGuard {
373        previous: std::path::PathBuf,
374    }
375
376    impl CwdGuard {
377        fn new() -> Self {
378            let previous = std::env::current_dir().expect("current dir");
379            Self { previous }
380        }
381    }
382
383    impl Drop for CwdGuard {
384        fn drop(&mut self) {
385            let _ = std::env::set_current_dir(&self.previous);
386        }
387    }
388
389    #[tokio::test]
390    async fn test_library_exports() {
391        // Test that all public exports are accessible
392        let _cache = PromptCache::new().await;
393    }
394
395    #[test]
396    fn test_module_structure() {
397        // Test that all modules can be imported
398        // This is a compile-time test that ensures module structure is correct
399    }
400
401    #[test]
402    fn test_version_consistency() {
403        // Test that version information is consistent across modules
404        // This would be more meaningful with actual version checking
405    }
406
407    #[tokio::test]
408    async fn test_tool_registry_integration() {
409        use crate::config::constants::tools;
410
411        let temp_dir = TempDir::new().expect("Failed to create temp dir");
412        let _cwd_guard = CwdGuard::new();
413        std::env::set_current_dir(&temp_dir).expect("Failed to change dir");
414
415        let registry = ToolRegistry::new(temp_dir.path().to_path_buf()).await;
416        registry
417            .initialize_async()
418            .await
419            .expect("Failed to init registry");
420
421        // Test that we can execute basic tools
422        let list_args = serde_json::json!({
423            "action": "list",
424            "path": "."
425        });
426
427        let result = registry
428            .execute_tool(tools::UNIFIED_SEARCH, list_args)
429            .await;
430        assert!(result.is_ok());
431
432        let response: serde_json::Value = result.expect("Failed to execute unified_search:list");
433        assert!(response.is_object() || response.is_array());
434    }
435
436    #[tokio::test]
437    async fn test_pty_basic_command() {
438        let temp_dir = TempDir::new().expect("Failed to create temp dir");
439        let workspace = temp_dir.path().to_path_buf();
440        let registry = ToolRegistry::new(workspace.clone()).await;
441        registry
442            .initialize_async()
443            .await
444            .expect("Failed to init registry");
445
446        // Test a simple PTY command
447        let args = serde_json::json!({
448            "command": "echo",
449            "args": ["Hello, PTY!"]
450        });
451
452        let result = registry.execute_tool("run_pty_cmd", args).await;
453        assert!(result.is_ok());
454        let response: serde_json::Value = result.expect("Failed to run PTY");
455        assert_eq!(response["is_exited"], true);
456        assert_eq!(response["exit_code"], 0);
457        assert!(response["output"].is_string());
458    }
459
460    #[tokio::test]
461    async fn test_pty_session_management() {
462        let temp_dir = TempDir::new().expect("Failed to create temp dir");
463        let workspace = temp_dir.path().to_path_buf();
464        let registry = ToolRegistry::new(workspace.clone()).await;
465        registry
466            .initialize_async()
467            .await
468            .expect("Failed to init registry");
469
470        // Test creating a PTY session
471        let args = serde_json::json!({
472            "command": "cat",
473            "yield_time_ms": 10
474        });
475
476        let result = registry.execute_tool("create_pty_session", args).await;
477        assert!(result.is_ok());
478        let response: serde_json::Value = result.expect("Failed to create PTY session");
479        assert_eq!(response["success"], true);
480        assert_eq!(response["is_exited"], false);
481        let session_id = response["session_id"]
482            .as_str()
483            .expect("create_pty_session should return a session id")
484            .to_string();
485        assert!(!session_id.is_empty());
486
487        // Test listing PTY sessions
488        let args = serde_json::json!({});
489        let result = registry.execute_tool("list_pty_sessions", args).await;
490        assert!(result.is_ok());
491        let response: serde_json::Value = result.expect("Failed to list PTY sessions");
492        assert!(response.is_object() || response.is_array());
493
494        // Test closing a PTY session
495        let args = serde_json::json!({
496            "session_id": session_id.clone()
497        });
498
499        let result = registry.execute_tool("close_pty_session", args).await;
500        assert!(result.is_ok());
501        let response: serde_json::Value = result.expect("Failed to close PTY session");
502        assert_eq!(response["success"], true);
503        assert_eq!(response["session_id"], session_id);
504    }
505}