use futures_util::StreamExt;
use super::client::GeminiClient;
use crate::entities::sampling::{ReasoningEffort, SamplingConfig};
use crate::shared::api::contract::{
ApiMessage, ChatChunk, ChatRequest, EngineBackend, FinishReason,
};
fn client_for(var: &str) -> Option<(String, GeminiClient)> {
let Ok(key) = std::env::var("MINDFORK_GEMINI_KEY") else {
eprintln!("skip: MINDFORK_GEMINI_KEY not set");
return None;
};
let Ok(model) = std::env::var(var) else {
eprintln!("skip: {var} not set");
return None;
};
let client = GeminiClient::new(
"https://generativelanguage.googleapis.com/v1beta",
key,
model.clone(),
);
Some((model, client))
}
fn title_turn() -> ChatRequest {
ChatRequest {
continue_final: false,
system: Some("Give this conversation a short title. Answer with the title only.".into()),
messages: vec![ApiMessage::user("How do database indexes work?")],
sampling: SamplingConfig {
max_tokens: Some(2048),
temperature: Some(0.3),
thinking: Some(false),
reasoning_effort: Some(ReasoningEffort::None),
reasoning_budget: Some(0),
..Default::default()
},
tools: vec![],
}
}
async fn muted_turn(client: &GeminiClient) -> (String, Option<FinishReason>) {
let mut stream = client
.chat_stream(title_turn(), Default::default())
.await
.expect("a muted turn must not be refused");
let (mut text, mut finish) = (String::new(), None);
while let Some(chunk) = stream.next().await {
match chunk {
ChatChunk::Text(t) => text.push_str(&t),
ChatChunk::Error { message, .. } => eprintln!("engine error: {message}"),
ChatChunk::Finished(r) => {
finish = Some(r);
break;
}
_ => {}
}
}
(text, finish)
}
#[tokio::test]
#[ignore = "requires MINDFORK_GEMINI_KEY + MINDFORK_LIVE_NO_MINIMAL_LEVEL_MODEL (a Gemini 3.x model, not a Pro, that refuses thinkingLevel \"minimal\")"]
async fn a_muted_turn_survives_a_model_without_the_minimal_level() {
let Some((model, client)) = client_for("MINDFORK_LIVE_NO_MINIMAL_LEVEL_MODEL") else {
return;
};
for turn in 1..=2 {
let (text, finish) = muted_turn(&client).await;
println!("{model} turn {turn}: finish={finish:?} title={text}");
assert!(
finish == Some(FinishReason::Stop) && !text.trim().is_empty(),
"{model}: the muted turn must complete: finish={finish:?} text={text:?}"
);
}
let learned = client.learned("minimal");
println!("{model}: asked instead of \"minimal\": {learned:?}");
assert!(
learned.is_some(),
"{model} was declared to refuse \"minimal\", yet nothing was refused — the recovery did not run"
);
}
#[tokio::test]
#[ignore = "requires MINDFORK_GEMINI_KEY + MINDFORK_LIVE_MINIMAL_LEVEL_MODEL (a Gemini 3.x model that takes thinkingLevel \"minimal\")"]
async fn a_muted_turn_on_a_model_with_the_minimal_level_is_sent_as_before() {
let Some((model, client)) = client_for("MINDFORK_LIVE_MINIMAL_LEVEL_MODEL") else {
return;
};
let (text, finish) = muted_turn(&client).await;
println!("{model}: finish={finish:?} title={text}");
assert!(
finish == Some(FinishReason::Stop) && !text.trim().is_empty(),
"{model}: finish={finish:?} text={text:?}"
);
assert_eq!(
client.learned("minimal"),
None,
"{model} was declared to take \"minimal\": nothing should have been learned"
);
}