vtcode-llm 0.181.3

LLM provider abstraction, client implementations, and streaming for VT Code
Documentation
use super::*;

#[test]
fn test_tool_result_blocks_empty() {
    let blocks = tool_result_blocks("");
    assert_eq!(blocks.len(), 1);
    assert_eq!(blocks[0]["type"], "text");
}

#[test]
fn test_tool_result_blocks_plain_text() {
    let blocks = tool_result_blocks("Hello world");
    assert_eq!(blocks.len(), 1);
    assert_eq!(blocks[0]["type"], "text");
    assert_eq!(blocks[0]["text"], "Hello world");
}

#[test]
fn test_tool_result_blocks_json_string() {
    let blocks = tool_result_blocks("\"Hello\"");
    assert_eq!(blocks.len(), 1);
    assert_eq!(blocks[0]["type"], "text");
    assert_eq!(blocks[0]["text"], "Hello");
}

#[test]
fn test_tool_result_blocks_json_object() {
    let blocks = tool_result_blocks("{\"key\": \"value\"}");
    assert_eq!(blocks.len(), 1);
    assert_eq!(blocks[0]["type"], "text");
    assert_eq!(blocks[0]["text"], "{\"key\":\"value\"}");
}

#[test]
fn test_convert_to_anthropic_format_adds_top_level_cache_control() {
    let request = LLMRequest {
        model: models::CLAUDE_SONNET_5.to_string(),
        messages: vec![Message::user("hello".to_string())].into(),
        ..Default::default()
    };
    let cache_settings = AnthropicPromptCacheSettings::default();
    let anthropic_config = AnthropicConfig::default();
    let ctx = RequestBuilderContext {
        prompt_cache_enabled: true,
        prompt_cache_settings: &cache_settings,
        anthropic_config: &anthropic_config,
        model: models::anthropic::DEFAULT_MODEL,
        server_side_fallbacks_available: false,
    };

    let payload = convert_to_anthropic_format(&request, &ctx).expect("payload conversion");

    assert_eq!(payload["cache_control"]["type"], "ephemeral");
    assert_eq!(payload["cache_control"]["ttl"], "5m");
}

#[test]
fn test_convert_to_anthropic_format_preserves_opus_5_mid_conversation_system_message() {
    let request = LLMRequest {
        model: models::CLAUDE_OPUS_5.to_string(),
        messages: vec![
            Message::user("Review this code.".to_string()),
            Message::system("From now on, every suggestion must include explicit type annotations.".to_string()),
        ]
        .into(),
        ..Default::default()
    };
    let cache_settings = AnthropicPromptCacheSettings::default();
    let anthropic_config = AnthropicConfig::default();
    let ctx = RequestBuilderContext {
        prompt_cache_enabled: true,
        prompt_cache_settings: &cache_settings,
        anthropic_config: &anthropic_config,
        model: models::anthropic::DEFAULT_MODEL,
        server_side_fallbacks_available: false,
    };

    let payload = convert_to_anthropic_format(&request, &ctx).expect("payload conversion");

    assert_eq!(payload["messages"][0]["role"], "user");
    assert_eq!(payload["messages"][1]["role"], "system");
    assert_eq!(
        payload["messages"][1]["content"][0]["text"],
        "From now on, every suggestion must include explicit type annotations."
    );
}

#[test]
fn test_convert_to_anthropic_format_preserves_opus_48_mid_conversation_system_message() {
    let request = LLMRequest {
        model: models::CLAUDE_OPUS_5.to_string(),
        messages: vec![
            Message::user("Review this code.".to_string()),
            Message::system("From now on, every suggestion must include explicit type annotations.".to_string()),
        ]
        .into(),
        ..Default::default()
    };
    let cache_settings = AnthropicPromptCacheSettings::default();
    let anthropic_config = AnthropicConfig::default();
    let ctx = RequestBuilderContext {
        prompt_cache_enabled: true,
        prompt_cache_settings: &cache_settings,
        anthropic_config: &anthropic_config,
        model: models::anthropic::DEFAULT_MODEL,
        server_side_fallbacks_available: false,
    };

    let payload = convert_to_anthropic_format(&request, &ctx).expect("payload conversion");

    assert_eq!(payload["messages"][0]["role"], "user");
    assert_eq!(payload["messages"][1]["role"], "system");
    assert_eq!(
        payload["messages"][1]["content"][0]["text"],
        "From now on, every suggestion must include explicit type annotations."
    );
}

#[test]
fn test_convert_to_anthropic_format_drops_mid_conversation_system_for_pre_opus_48() {
    let request = LLMRequest {
        model: models::CLAUDE_SONNET_5.to_string(),
        messages: vec![
            Message::user("Review this code.".to_string()),
            Message::system("Use strict typing.".to_string()),
        ]
        .into(),
        ..Default::default()
    };
    let cache_settings = AnthropicPromptCacheSettings::default();
    let anthropic_config = AnthropicConfig::default();
    let ctx = RequestBuilderContext {
        prompt_cache_enabled: true,
        prompt_cache_settings: &cache_settings,
        anthropic_config: &anthropic_config,
        model: models::anthropic::DEFAULT_MODEL,
        server_side_fallbacks_available: false,
    };

    let payload = convert_to_anthropic_format(&request, &ctx).expect("payload conversion");

    assert_eq!(payload["messages"].as_array().unwrap().len(), 1);
    assert_eq!(payload["messages"][0]["role"], "user");
}

#[test]
fn test_convert_to_anthropic_format_uses_native_structured_outputs() {
    let request = LLMRequest {
        model: models::CLAUDE_SONNET_5.to_string(),
        messages: vec![Message::user("hello".to_string())].into(),
        output_format: Some(json!({
            "type": "object",
            "properties": {
                "answer": {"type": "string"}
            },
            "required": ["answer"],
            "additionalProperties": false
        })),
        ..Default::default()
    };
    let cache_settings = AnthropicPromptCacheSettings::default();
    let anthropic_config = AnthropicConfig::default();
    let ctx = RequestBuilderContext {
        prompt_cache_enabled: false,
        prompt_cache_settings: &cache_settings,
        anthropic_config: &anthropic_config,
        model: models::anthropic::DEFAULT_MODEL,
        server_side_fallbacks_available: false,
    };

    let payload = convert_to_anthropic_format(&request, &ctx).expect("payload conversion");

    assert_eq!(payload["output_config"]["format"]["type"], "json_schema");
    assert_eq!(payload["output_config"]["format"]["schema"]["required"], json!(["answer"]));
    assert!(payload.get("tools").is_none());
}