terraphim_multi_agent 1.0.0

Multi-agent system for Terraphim built on roles with rust-genai integration
Documentation
use terraphim_multi_agent::{test_utils::*, *};

fn ollama_available() -> bool {
    // Treat these as integration-style tests: they require a local Ollama instance
    // with the configured model pulled.
    std::env::var("RUN_OLLAMA_TESTS").ok().as_deref() == Some("1")
}

#[tokio::test]
async fn test_generate_command_processing() {
    if !ollama_available() {
        eprintln!("Skipping: set RUN_OLLAMA_TESTS=1 and ensure Ollama has model gemma3:270m");
        return;
    }
    let agent = create_test_agent().await.unwrap();
    agent.initialize().await.unwrap();

    let input = CommandInput::new(
        "Write a hello world function in Rust".to_string(),
        CommandType::Generate,
    );
    let result = agent.process_command(input).await;

    assert!(
        result.is_ok(),
        "Generate command should succeed, got error: {:?}",
        result.as_ref().err()
    );
    let output = result.unwrap();

    // Verify output structure
    assert!(
        !output.text.is_empty(),
        "Output should contain generated text"
    );
    // Check confidence score if present
    if let Some(confidence) = output.confidence {
        assert!(
            (0.0..=1.0).contains(&confidence),
            "Confidence score should be normalized"
        );
    }
}

#[tokio::test]
async fn test_answer_command_processing() {
    if !ollama_available() {
        eprintln!("Skipping: set RUN_OLLAMA_TESTS=1 and ensure Ollama has model gemma3:270m");
        return;
    }
    let agent = create_test_agent().await.unwrap();
    agent.initialize().await.unwrap();

    let input = CommandInput::new(
        "What is Rust programming language?".to_string(),
        CommandType::Answer,
    );
    let result = agent.process_command(input).await;

    assert!(result.is_ok(), "Answer command should succeed");
    let output = result.unwrap();

    assert!(!output.text.is_empty(), "Answer should contain text");
}

#[tokio::test]
async fn test_analyze_command_processing() {
    if !ollama_available() {
        eprintln!("Skipping: set RUN_OLLAMA_TESTS=1 and ensure Ollama has model gemma3:270m");
        return;
    }
    let agent = create_test_agent().await.unwrap();
    agent.initialize().await.unwrap();

    let input = CommandInput::new(
        "Analyze the performance characteristics of HashMap vs BTreeMap".to_string(),
        CommandType::Analyze,
    );
    let result = agent.process_command(input).await;

    assert!(result.is_ok(), "Analyze command should succeed");
    let output = result.unwrap();

    assert!(!output.text.is_empty(), "Analysis should contain text");
}

#[tokio::test]
async fn test_create_command_processing() {
    if !ollama_available() {
        eprintln!("Skipping: set RUN_OLLAMA_TESTS=1 and ensure Ollama has model gemma3:270m");
        return;
    }
    let agent = create_test_agent().await.unwrap();
    agent.initialize().await.unwrap();

    let input = CommandInput::new(
        "Create a new API endpoint design for user authentication".to_string(),
        CommandType::Create,
    );
    let result = agent.process_command(input).await;

    assert!(result.is_ok(), "Create command should succeed");
    let output = result.unwrap();

    assert!(!output.text.is_empty(), "Creation should contain text");
}

#[tokio::test]
async fn test_review_command_processing() {
    if !ollama_available() {
        eprintln!("Skipping: set RUN_OLLAMA_TESTS=1 and ensure Ollama has model gemma3:270m");
        return;
    }
    let agent = create_test_agent().await.unwrap();
    agent.initialize().await.unwrap();

    let input = CommandInput::new(
        "Review this code: fn main() { println!(\"Hello\"); }".to_string(),
        CommandType::Review,
    );
    let result = agent.process_command(input).await;

    assert!(result.is_ok(), "Review command should succeed");
    let output = result.unwrap();

    assert!(!output.text.is_empty(), "Review should contain text");
}

#[tokio::test]
async fn test_command_with_context() {
    if !ollama_available() {
        eprintln!("Skipping: set RUN_OLLAMA_TESTS=1 and ensure Ollama has model gemma3:270m");
        return;
    }
    let agent = create_test_agent().await.unwrap();
    agent.initialize().await.unwrap();

    // First add some context to the agent
    {
        let mut context = agent.context.write().await;
        context
            .add_item(ContextItem::new(
                ContextItemType::Memory,
                "User prefers functional programming patterns".to_string(),
                30, // token count
                0.8,
            ))
            .unwrap();
        context
            .add_item(ContextItem::new(
                ContextItemType::Task,
                "Working on a web API project".to_string(),
                25, // token count
                0.9,
            ))
            .unwrap();
    }

    let input = CommandInput::new(
        "Write a function to handle HTTP requests".to_string(),
        CommandType::Generate,
    );
    let result = agent.process_command(input).await;

    assert!(result.is_ok(), "Command with context should succeed");
    let output = result.unwrap();

    // The mock should have included context in the response
    assert!(!output.text.is_empty());
}

#[tokio::test]
async fn test_command_tracking() {
    if !ollama_available() {
        eprintln!("Skipping: set RUN_OLLAMA_TESTS=1 and ensure Ollama has model gemma3:270m");
        return;
    }
    let agent = create_test_agent().await.unwrap();
    agent.initialize().await.unwrap();

    let input = CommandInput::new(
        "Test command for tracking".to_string(),
        CommandType::Generate,
    );
    let result = agent.process_command(input).await;
    assert!(result.is_ok());

    // Verify command was tracked
    let history = agent.command_history.read().await;
    assert_eq!(
        history.records.len(),
        1,
        "Command should be recorded in history"
    );

    let recorded_command = &history.records[0];
    assert_eq!(recorded_command.input.command_type, CommandType::Generate);
    assert_eq!(recorded_command.input.text, "Test command for tracking");
    assert!(!recorded_command.output.text.is_empty());

    // Verify token tracking
    let token_tracker = agent.token_tracker.read().await;
    assert!(
        token_tracker.total_input_tokens + token_tracker.total_output_tokens > 0,
        "Should have recorded token usage"
    );

    // Verify cost tracking
    let cost_tracker = agent.cost_tracker.read().await;
    assert!(
        cost_tracker.current_month_spending >= 0.0,
        "Should have recorded costs"
    );
}

#[tokio::test]
async fn test_concurrent_command_processing() {
    if !ollama_available() {
        eprintln!("Skipping: set RUN_OLLAMA_TESTS=1 and ensure Ollama has model gemma3:270m");
        return;
    }
    let agent = create_test_agent().await.unwrap();
    agent.initialize().await.unwrap();

    use tokio::task::JoinSet;
    let mut join_set = JoinSet::new();

    // Process multiple commands concurrently
    let commands = vec![
        ("Generate", "Write hello world"),
        ("Answer", "What is Rust?"),
        ("Analyze", "Compare Vec and LinkedList"),
        ("Create", "Design a REST API"),
        ("Review", "fn test() {}"),
    ];

    for (i, (cmd_type, prompt)) in commands.into_iter().enumerate() {
        let agent_clone = agent.clone();
        join_set.spawn(async move {
            let cmd_type = match cmd_type {
                "Generate" => CommandType::Generate,
                "Answer" => CommandType::Answer,
                "Analyze" => CommandType::Analyze,
                "Create" => CommandType::Create,
                "Review" => CommandType::Review,
                _ => CommandType::Generate,
            };
            let input = CommandInput::new(prompt.to_string(), cmd_type);
            (i, agent_clone.process_command(input).await)
        });
    }

    let mut results = Vec::new();
    while let Some(result) = join_set.join_next().await {
        results.push(result.unwrap());
    }

    // All commands should succeed
    assert_eq!(results.len(), 5);
    for (i, result) in results {
        assert!(result.is_ok(), "Concurrent command {} should succeed", i);
    }

    // Verify all commands were tracked
    let history = agent.command_history.read().await;
    assert_eq!(history.records.len(), 5, "All commands should be tracked");
}

#[tokio::test]
async fn test_command_input_validation() {
    if !ollama_available() {
        eprintln!("Skipping: set RUN_OLLAMA_TESTS=1 and ensure Ollama has model gemma3:270m");
        return;
    }
    let agent = create_test_agent().await.unwrap();
    agent.initialize().await.unwrap();

    // Test empty prompt
    let input = CommandInput::new("".to_string(), CommandType::Generate);
    let result = agent.process_command(input).await;

    // Should still work (mock LLM will handle empty input)
    assert!(result.is_ok(), "Empty prompt should be handled gracefully");
}

#[tokio::test]
async fn test_command_quality_scoring() {
    if !ollama_available() {
        eprintln!("Skipping: set RUN_OLLAMA_TESTS=1 and ensure Ollama has model gemma3:270m");
        return;
    }
    let agent = create_test_agent().await.unwrap();
    agent.initialize().await.unwrap();

    let input = CommandInput::new(
        "Write excellent Rust code".to_string(),
        CommandType::Generate,
    );
    let result = agent.process_command(input).await;
    assert!(result.is_ok());

    let output = result.unwrap();

    // Output should contain generated text
    assert!(
        !output.text.is_empty(),
        "Generated output should not be empty"
    );
}

#[tokio::test]
async fn test_context_injection() {
    if !ollama_available() {
        eprintln!("Skipping: set RUN_OLLAMA_TESTS=1 and ensure Ollama has model gemma3:270m");
        return;
    }
    let agent = create_test_agent().await.unwrap();
    agent.initialize().await.unwrap();

    // Add relevant context
    {
        let mut context = agent.context.write().await;
        context
            .add_item(ContextItem::new(
                ContextItemType::Memory,
                "User is working on performance optimization".to_string(),
                35, // token count
                0.95,
            ))
            .unwrap();
    }

    let input = CommandInput::new(
        "How to optimize this code?".to_string(),
        CommandType::Analyze,
    );
    let result = agent.process_command(input).await;
    assert!(result.is_ok());

    let output = result.unwrap();
    assert!(
        !output.text.is_empty(),
        "Output should contain analysis text"
    );
}

#[tokio::test]
async fn test_command_temperature_control() {
    if !ollama_available() {
        eprintln!("Skipping: set RUN_OLLAMA_TESTS=1 and ensure Ollama has model gemma3:270m");
        return;
    }
    let agent = create_test_agent().await.unwrap();
    agent.initialize().await.unwrap();

    // Different command types should use appropriate temperatures
    // (This is mainly testing that the commands execute with different configs)

    let creative_input =
        CommandInput::new("Write creative content".to_string(), CommandType::Generate);
    let creative_result = agent.process_command(creative_input).await;
    assert!(creative_result.is_ok());

    let analytical_input = CommandInput::new("Analyze this data".to_string(), CommandType::Analyze);
    let analytical_result = agent.process_command(analytical_input).await;
    assert!(analytical_result.is_ok());

    // Both should succeed (temperature differences handled by LLM client)
    assert!(!creative_result.unwrap().text.is_empty());
    assert!(!analytical_result.unwrap().text.is_empty());
}