vv-agent 0.4.1

VectorVein agent runtime, SDK, CLI, tools, and workspace backends
Documentation
use serde_json::json;
use vv_agent::{
    memory::token_utils::{
        count_messages_tokens, count_tokens, estimate_tokens, resolve_model_token_limits,
        resolve_model_token_limits_from_file,
    },
    Message,
};

#[test]
fn count_tokens_prefers_vv_llm_counter() {
    let text = "hello world";
    let expected = vv_llm::utilities::count_tokens(text, "gpt-4o").expect("vv-llm count");

    assert_eq!(count_tokens(text, "gpt-4o"), expected as u64);
}

#[test]
fn count_tokens_returns_zero_for_empty_text() {
    assert_eq!(count_tokens("", "gpt-4o"), 0);
}

#[test]
fn count_tokens_falls_back_to_cjk_aware_estimate_for_unknown_models() {
    assert_eq!(
        count_tokens("你好hello", "demo"),
        estimate_tokens("你好hello", "demo")
    );
}

#[test]
fn count_tokens_accepts_json_payload() {
    let payload = json!({"role": "user", "content": "hello"});
    let expected_payload = serde_json::to_string(&payload).expect("json payload");

    assert_eq!(
        count_tokens(&payload, "demo"),
        estimate_tokens(&expected_payload, "demo")
    );
}

#[test]
fn count_messages_tokens_fallback_uses_openai_message_payload() {
    let mut message = Message::user("look");
    message.image_url = Some("https://example.test/image.png".to_string());
    let payload = vec![message.to_openai_message(true)];
    let expected_payload = serde_json::to_string(&payload).expect("message payload");

    assert_eq!(
        count_messages_tokens(&[message], "unknown-provider-model"),
        estimate_tokens(&expected_payload, "unknown-provider-model")
    );
}

#[test]
fn estimate_tokens_handles_cjk_and_ascii_mix() {
    assert_eq!(estimate_tokens("你好", "demo"), 3);
    assert_eq!(estimate_tokens("hello", "demo"), 1);
    assert_eq!(estimate_tokens("你好hello", "demo"), 4);
}

#[test]
fn resolve_model_token_limits_reads_vv_llm_settings_model_config() {
    let settings = json!({
        "VERSION": "2",
        "endpoints": [{"id": "openai-default", "api_base": "https://example.test", "api_key": "sk-test"}],
        "backends": {
            "openai": {
                "models": {
                    "gpt-demo": {
                        "id": "provider-gpt-demo",
                        "endpoints": ["openai-default"],
                        "context_length": 64000,
                        "max_output_tokens": 8000
                    }
                }
            }
        }
    });

    assert_eq!(
        resolve_model_token_limits(&settings, "openai", "gpt-demo"),
        (Some(64_000), Some(8_000))
    );
    assert_eq!(
        resolve_model_token_limits(&settings, "openai", "provider-gpt-demo"),
        (Some(64_000), Some(8_000))
    );
    assert_eq!(
        resolve_model_token_limits(&settings, "openai", "missing"),
        (None, None)
    );
}

#[test]
fn resolve_model_token_limits_from_file_accepts_json_settings_loader() {
    let settings = tempfile::NamedTempFile::new().expect("settings file");
    std::fs::write(
        settings.path(),
        r#"{
          "VERSION": "2",
          "endpoints": [{"id": "deepseek-default", "api_base": "https://example.test", "api_key": "sk-test"}],
          "backends": {
            "deepseek": {
              "models": {
                "deepseek-v4-pro": {
                  "id": "deepseek-v4-pro",
                  "endpoints": ["deepseek-default"],
                  "context_length": 131072,
                  "max_output_tokens": 8192
                }
              }
            }
          }
        }"#,
    )
    .expect("write settings");

    assert_eq!(
        resolve_model_token_limits_from_file(settings.path(), "deepseek", "deepseek-v4-pro"),
        (Some(131_072), Some(8_192))
    );
}