vtcode 0.99.1

A Rust-based terminal coding agent with modular architecture supporting multiple LLM providers
use std::collections::BTreeMap;
use std::fs;

use tempfile::tempdir;
use vtcode_core::config::core::PromptCachingConfig;
use vtcode_core::config::loader::VTCodeConfig;
use vtcode_core::config::models::Provider;
use vtcode_core::config::types::AgentConfig as CoreAgentConfig;
use vtcode_core::config::types::{ModelSelectionSource, ReasoningEffortLevel, UiSurfacePreference};
use vtcode_core::core::agent::snapshots::{
    DEFAULT_CHECKPOINTS_ENABLED, DEFAULT_MAX_AGE_DAYS, DEFAULT_MAX_SNAPSHOTS,
};

use super::welcome::prepare_session_bootstrap;

#[tokio::test]
async fn test_prepare_session_bootstrap_builds_sections() {
    let tmp = tempdir().expect("Failed to create temp directory");
    fs::write(
        tmp.path().join("Cargo.toml"),
        "[package]\nname = \"demo\"\nversion = \"0.1.0\"\ndescription = \"Demo project\"\n",
    )
    .expect("Failed to write Cargo.toml");
    fs::create_dir_all(tmp.path().join("src")).expect("Failed to create src directory");
    fs::write(tmp.path().join("src/main.rs"), "fn main() {}\n").expect("Failed to write main.rs");
    fs::write(
        tmp.path().join("AGENTS.md"),
        "- Follow workspace guidelines\n- Prefer 4-space indentation\n- Run cargo fmt before commits\n",
    )
    .expect("Failed to write AGENTS.md");
    fs::write(tmp.path().join("README.md"), "Demo workspace\n").expect("Failed to write README.md");

    let mut vt_cfg = VTCodeConfig::default();
    vt_cfg.agent.onboarding.include_language_summary = false;
    vt_cfg.agent.onboarding.guideline_highlight_limit = 2;
    vt_cfg.agent.onboarding.include_usage_tips_in_welcome = true;
    vt_cfg
        .agent
        .onboarding
        .include_recommended_actions_in_welcome = true;
    vt_cfg.agent.onboarding.usage_tips = vec!["Tip one".into()];
    vt_cfg.agent.onboarding.recommended_actions = vec!["Do something".into()];
    vt_cfg.agent.onboarding.chat_placeholder = Some("Type your plan".into());

    let runtime_cfg = CoreAgentConfig {
        model: vtcode_core::config::constants::models::google::GEMINI_3_FLASH_PREVIEW.to_string(),
        api_key: "test".to_string(),
        provider: "gemini".to_string(),
        api_key_env: Provider::Gemini.default_api_key_env().to_string(),
        workspace: tmp.path().to_path_buf(),
        verbose: false,
        theme: vtcode_core::ui::theme::DEFAULT_THEME_ID.to_string(),
        reasoning_effort: ReasoningEffortLevel::default(),
        ui_surface: UiSurfacePreference::default(),
        prompt_cache: PromptCachingConfig::default(),
        model_source: ModelSelectionSource::WorkspaceConfig,
        custom_api_keys: BTreeMap::new(),
        checkpointing_enabled: DEFAULT_CHECKPOINTS_ENABLED,
        checkpointing_storage_dir: None,
        checkpointing_max_snapshots: DEFAULT_MAX_SNAPSHOTS,
        checkpointing_max_age_days: Some(DEFAULT_MAX_AGE_DAYS),
        quiet: false,
        max_conversation_turns: 1000,
        model_behavior: None,
        openai_chatgpt_auth: None,
    };

    let bootstrap = prepare_session_bootstrap(&runtime_cfg, Some(&vt_cfg), None).await;

    assert_eq!(bootstrap.header_highlights.len(), 4);

    let slash_commands = &bootstrap.header_highlights[0];
    assert!(slash_commands.title.is_empty());
    assert!(
        slash_commands
            .lines
            .iter()
            .any(|line| line.contains("/{command}"))
    );
    assert!(
        slash_commands
            .lines
            .iter()
            .any(|line| line.contains("/help"))
    );
    assert!(
        slash_commands
            .lines
            .iter()
            .any(|line| line.contains("Enter"))
    );
    assert!(
        slash_commands
            .lines
            .iter()
            .any(|line| line.contains("Escape"))
    );

    let usage_tips = &bootstrap.header_highlights[1];
    assert_eq!(usage_tips.title, "Usage Tips");
    assert!(usage_tips.lines.iter().any(|line| line.contains("Tip one")));

    let recommended_actions = &bootstrap.header_highlights[2];
    assert_eq!(recommended_actions.title, "Suggested Next Actions");
    assert!(
        recommended_actions
            .lines
            .iter()
            .any(|line| line.contains("Do something"))
    );

    let prompt = bootstrap.prompt_addendum.expect("prompt addendum");
    assert!(prompt.contains("## SESSION CONTEXT"));
    assert!(prompt.contains("Workflow Hint"));
    assert!(prompt.contains("@file"));
    assert!(prompt.contains("Plan Mode"));
    assert!(prompt.contains("task_tracker"));
    assert!(prompt.contains("Suggested Next Actions"));

    assert_eq!(bootstrap.placeholder.as_deref(), Some("Type your plan"));
}

#[tokio::test]
async fn test_welcome_hides_optional_sections_by_default() {
    let tmp = tempdir().expect("Failed to create temp directory");
    fs::write(
        tmp.path().join("Cargo.toml"),
        "[package]\nname = \"demo\"\nversion = \"0.1.0\"\ndescription = \"Demo project\"\n",
    )
    .expect("Failed to write Cargo.toml");
    fs::write(tmp.path().join("README.md"), "Demo workspace\n").expect("Failed to write README.md");

    let runtime_cfg = CoreAgentConfig {
        model: vtcode_core::config::constants::models::google::GEMINI_3_FLASH_PREVIEW.to_string(),
        api_key: "test".to_string(),
        provider: "gemini".to_string(),
        api_key_env: Provider::Gemini.default_api_key_env().to_string(),
        workspace: tmp.path().to_path_buf(),
        verbose: false,
        theme: vtcode_core::ui::theme::DEFAULT_THEME_ID.to_string(),
        reasoning_effort: ReasoningEffortLevel::default(),
        ui_surface: UiSurfacePreference::default(),
        prompt_cache: PromptCachingConfig::default(),
        model_source: ModelSelectionSource::WorkspaceConfig,
        custom_api_keys: BTreeMap::new(),
        checkpointing_enabled: DEFAULT_CHECKPOINTS_ENABLED,
        checkpointing_storage_dir: None,
        checkpointing_max_snapshots: DEFAULT_MAX_SNAPSHOTS,
        checkpointing_max_age_days: Some(DEFAULT_MAX_AGE_DAYS),
        quiet: false,
        max_conversation_turns: 1000,
        model_behavior: None,
        openai_chatgpt_auth: None,
    };

    let vt_cfg = VTCodeConfig::default();
    let bootstrap = prepare_session_bootstrap(&runtime_cfg, Some(&vt_cfg), None).await;

    assert_eq!(bootstrap.header_highlights.len(), 2);
    let slash_commands = &bootstrap.header_highlights[0];
    assert!(slash_commands.title.is_empty());
    assert!(
        slash_commands
            .lines
            .iter()
            .any(|line| line.contains("/{command}"))
    );
    assert!(
        slash_commands
            .lines
            .iter()
            .any(|line| line.contains("Enter"))
    );
    assert!(
        slash_commands
            .lines
            .iter()
            .any(|line| line.contains("Escape"))
    );
}

#[tokio::test]
async fn test_prepare_session_bootstrap_hides_placeholder_when_planning_disabled() {
    let tmp = tempdir().expect("Failed to create temp directory");
    fs::write(
        tmp.path().join("Cargo.toml"),
        "[package]\nname = \"demo\"\nversion = \"0.1.0\"\ndescription = \"Demo\"\n",
    )
    .expect("Failed to write Cargo.toml");
    fs::create_dir_all(tmp.path().join("src")).expect("Failed to create src directory");
    fs::write(tmp.path().join("src/lib.rs"), "pub fn demo() {}\n").expect("Failed to write lib.rs");

    let mut vt_cfg = VTCodeConfig::default();
    vt_cfg.agent.todo_planning_mode = false;
    vt_cfg.agent.onboarding.chat_placeholder = Some("Type your plan".into());

    let runtime_cfg = CoreAgentConfig {
        model: vtcode_core::config::constants::models::google::GEMINI_3_FLASH_PREVIEW.to_string(),
        api_key: "test".to_string(),
        provider: "gemini".to_string(),
        api_key_env: Provider::Gemini.default_api_key_env().to_string(),
        workspace: tmp.path().to_path_buf(),
        verbose: false,
        theme: vtcode_core::ui::theme::DEFAULT_THEME_ID.to_string(),
        reasoning_effort: ReasoningEffortLevel::default(),
        ui_surface: UiSurfacePreference::default(),
        prompt_cache: PromptCachingConfig::default(),
        model_source: ModelSelectionSource::WorkspaceConfig,
        custom_api_keys: BTreeMap::new(),
        checkpointing_enabled: DEFAULT_CHECKPOINTS_ENABLED,
        checkpointing_storage_dir: None,
        checkpointing_max_snapshots: DEFAULT_MAX_SNAPSHOTS,
        checkpointing_max_age_days: Some(DEFAULT_MAX_AGE_DAYS),
        quiet: false,
        max_conversation_turns: 1000,
        model_behavior: None,
        openai_chatgpt_auth: None,
    };

    let bootstrap = prepare_session_bootstrap(&runtime_cfg, Some(&vt_cfg), None).await;
    assert!(bootstrap.placeholder.is_none());

    if let Some(prompt) = bootstrap.prompt_addendum.as_deref() {
        assert!(!prompt.contains("Workflow Hint"));
        assert!(!prompt.contains("task_tracker"));
    }
}

#[tokio::test]
async fn test_prepare_session_bootstrap_does_not_surface_acp_trust_in_terminal_session() {
    let tmp = tempdir().expect("Failed to create temp directory");
    fs::write(
        tmp.path().join("Cargo.toml"),
        "[package]\nname = \"demo\"\nversion = \"0.1.0\"\ndescription = \"Demo\"\n",
    )
    .expect("Failed to write Cargo.toml");

    let runtime_cfg = CoreAgentConfig {
        model: vtcode_core::config::constants::models::google::GEMINI_3_FLASH_PREVIEW.to_string(),
        api_key: "test".to_string(),
        provider: "gemini".to_string(),
        api_key_env: Provider::Gemini.default_api_key_env().to_string(),
        workspace: tmp.path().to_path_buf(),
        verbose: false,
        theme: vtcode_core::ui::theme::DEFAULT_THEME_ID.to_string(),
        reasoning_effort: ReasoningEffortLevel::default(),
        ui_surface: UiSurfacePreference::default(),
        prompt_cache: PromptCachingConfig::default(),
        model_source: ModelSelectionSource::WorkspaceConfig,
        custom_api_keys: BTreeMap::new(),
        checkpointing_enabled: DEFAULT_CHECKPOINTS_ENABLED,
        checkpointing_storage_dir: None,
        checkpointing_max_snapshots: DEFAULT_MAX_SNAPSHOTS,
        checkpointing_max_age_days: Some(DEFAULT_MAX_AGE_DAYS),
        quiet: false,
        max_conversation_turns: 1000,
        model_behavior: None,
        openai_chatgpt_auth: None,
    };

    let mut vt_cfg = VTCodeConfig::default();
    vt_cfg.acp.enabled = true;
    vt_cfg.acp.zed.enabled = true;
    vt_cfg.acp.zed.workspace_trust =
        vtcode_core::config::AgentClientProtocolZedWorkspaceTrustMode::FullAuto;

    let bootstrap = prepare_session_bootstrap(&runtime_cfg, Some(&vt_cfg), None).await;

    assert!(bootstrap.acp_workspace_trust.is_none());
}