yoagent 0.26.4

The agent loop for Rust: stream from 7 LLM protocols, run tools, loop until done. Native or wasm32 (Cloudflare Workers).
Documentation
//! Integration tests against the real Google Gemini API.
//! Run with: GEMINI_API_KEY=... cargo test --test integration_gemini -- --ignored

use tokio::sync::mpsc;
use tokio_util::sync::CancellationToken;
use yoagent::agent_loop::{agent_loop, AgentLoopConfig};
use yoagent::provider::{GoogleProvider, ModelConfig};
use yoagent::tools;
use yoagent::types::*;

fn api_key() -> String {
    std::env::var("GEMINI_API_KEY").expect("GEMINI_API_KEY must be set")
}

fn make_config(model: &str) -> AgentLoopConfig {
    let model_config = ModelConfig::google(model, model);
    let mut config = AgentLoopConfig::new(std::sync::Arc::new(GoogleProvider), model);
    config.api_key = api_key();
    config.max_tokens = Some(1024);
    config.model_config = Some(model_config);
    config.cache_config = CacheConfig::disabled();
    config
}

fn extract_assistant_text(messages: &[AgentMessage]) -> String {
    messages
        .iter()
        .filter_map(|m| {
            if let AgentMessage::Llm(Message::Assistant { content, .. }) = m {
                Some(
                    content
                        .iter()
                        .filter_map(|c| {
                            if let Content::Text { text } = c {
                                Some(text.as_str())
                            } else {
                                None
                            }
                        })
                        .collect::<Vec<_>>()
                        .join(""),
                )
            } else {
                None
            }
        })
        .collect::<Vec<_>>()
        .join(" ")
}

/// Simple text response.
#[tokio::test]
#[ignore]
async fn test_gemini_simple_text() {
    let config = make_config("gemini-3-flash-preview");
    let (tx, _rx) = mpsc::unbounded_channel();
    let cancel = CancellationToken::new();

    let mut context = AgentContext {
        system_prompt: "Reply with exactly one word.".into(),
        messages: Vec::new(),
        tools: Vec::new(),
    };

    let prompt = AgentMessage::Llm(Message::user("What color is the sky?"));
    let new_messages = agent_loop(vec![prompt], &mut context, &config, tx, cancel).await;

    let text = extract_assistant_text(&new_messages);
    assert!(!text.is_empty(), "Expected non-empty response");
    println!("Response: {}", text);
}

/// Function tool use with Gemini.
#[tokio::test]
#[ignore]
async fn test_gemini_tool_use() {
    let config = make_config("gemini-3-flash-preview");
    let (tx, mut rx) = mpsc::unbounded_channel();
    let cancel = CancellationToken::new();

    let mut context = AgentContext {
        system_prompt: "Use the bash tool to answer. Be concise.".into(),
        messages: Vec::new(),
        tools: tools::default_tools(),
    };

    let prompt = AgentMessage::Llm(Message::user(
        "What is the output of `echo hello_gemini`? Use bash to run it.",
    ));
    let new_messages = agent_loop(vec![prompt], &mut context, &config, tx, cancel).await;

    let text = extract_assistant_text(&new_messages);
    assert!(
        text.contains("hello_gemini"),
        "Expected response with tool output, got: {}",
        text
    );

    let mut got_tool_execution = false;
    while let Ok(event) = rx.try_recv() {
        if matches!(event, AgentEvent::ToolExecutionStart { .. }) {
            got_tool_execution = true;
        }
    }
    assert!(got_tool_execution, "Expected tool execution");
    println!("Response: {}", text);
}