Skip to main content

vtcode_core/
lib.rs

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