use futures_util::StreamExt;
use super::client::AnthropicClient;
use crate::entities::sampling::{ReasoningEffort, SamplingConfig};
use crate::shared::api::contract::{
ApiMessage, ChatChunk, ChatRequest, EngineBackend, FinishReason,
};
fn client_for(var: &str) -> Option<(String, AnthropicClient)> {
let Ok(key) = std::env::var("MINDFORK_ANTHROPIC_KEY") else {
eprintln!("skip: MINDFORK_ANTHROPIC_KEY not set");
return None;
};
let Ok(model) = std::env::var(var) else {
eprintln!("skip: {var} not set");
return None;
};
let client = AnthropicClient::new("https://api.anthropic.com", key, model.clone());
Some((model, client))
}
async fn turn_at(
client: &AnthropicClient,
effort: ReasoningEffort,
) -> (String, Option<FinishReason>) {
let req = ChatRequest {
continue_final: false,
system: None,
messages: vec![ApiMessage::user("Reply with exactly: pong")],
sampling: SamplingConfig {
max_tokens: Some(2048),
thinking: Some(true),
reasoning_effort: Some(effort),
..Default::default()
},
tools: vec![],
};
let mut stream = client
.chat_stream(req, Default::default())
.await
.expect("the 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_ANTHROPIC_KEY + MINDFORK_LIVE_NO_EFFORT_XHIGH_MODEL (a Claude model with adaptive thinking and no \"xhigh\" level)"]
async fn a_level_the_model_lacks_becomes_the_nearest_it_lists() {
let Some((model, client)) = client_for("MINDFORK_LIVE_NO_EFFORT_XHIGH_MODEL") else {
return;
};
for nth in 1..=2 {
let (text, finish) = turn_at(&client, ReasoningEffort::XHigh).await;
println!("{model} turn {nth}: finish={finish:?} reply={text}");
assert!(
finish == Some(FinishReason::Stop) && !text.trim().is_empty(),
"{model}: the turn must complete: finish={finish:?} text={text:?}"
);
}
let learned = client.learned("xhigh");
println!("{model}: asked instead of \"xhigh\": {learned:?}");
assert!(
matches!(learned, Some(Some(_))),
"{model} was declared to refuse \"xhigh\" and to list what it takes: {learned:?}"
);
}
#[tokio::test]
#[ignore = "requires MINDFORK_ANTHROPIC_KEY + MINDFORK_LIVE_EFFORT_XHIGH_MODEL (a Claude model that takes \"xhigh\" and \"max\")"]
async fn the_top_levels_are_sent_as_they_are() {
let Some((model, client)) = client_for("MINDFORK_LIVE_EFFORT_XHIGH_MODEL") else {
return;
};
for effort in [ReasoningEffort::XHigh, ReasoningEffort::Max] {
let (text, finish) = turn_at(&client, effort).await;
println!("{model} at {effort:?}: finish={finish:?} reply={text}");
assert!(
finish == Some(FinishReason::Stop) && !text.trim().is_empty(),
"{model} at {effort:?}: finish={finish:?} text={text:?}"
);
assert_eq!(
client.learned(effort.as_wire()),
None,
"{model} was declared to take {effort:?}: nothing should have been learned"
);
}
assert!(
!client.thinks_within_a_budget(),
"{model} thinks adaptively: the budget shape must not have been used"
);
}
#[tokio::test]
#[ignore = "requires MINDFORK_ANTHROPIC_KEY + MINDFORK_LIVE_NO_ADAPTIVE_THINKING_MODEL (a Claude model without adaptive thinking)"]
async fn a_thinking_turn_survives_a_model_without_adaptive_thinking() {
let Some((model, client)) = client_for("MINDFORK_LIVE_NO_ADAPTIVE_THINKING_MODEL") else {
return;
};
for nth in 1..=2 {
let req = ChatRequest {
continue_final: false,
system: None,
messages: vec![ApiMessage::user(
"What is 17 * 23? Think it through, then answer with the number.",
)],
sampling: SamplingConfig {
max_tokens: Some(16384),
thinking: Some(true),
reasoning_effort: Some(ReasoningEffort::High),
..Default::default()
},
tools: vec![],
};
let mut stream = client
.chat_stream(req, Default::default())
.await
.expect("a thinking turn must not be refused");
let (mut text, mut thoughts, mut finish) = (String::new(), String::new(), None);
while let Some(chunk) = stream.next().await {
match chunk {
ChatChunk::Text(t) => text.push_str(&t),
ChatChunk::Thoughts(t) => thoughts.push_str(&t),
ChatChunk::Error { message, .. } => eprintln!("engine error: {message}"),
ChatChunk::Finished(r) => {
finish = Some(r);
break;
}
_ => {}
}
}
println!(
"{model} turn {nth}: finish={finish:?} thoughts={} chars reply={}",
thoughts.len(),
text.trim()
);
assert!(
finish == Some(FinishReason::Stop) && text.contains("391"),
"{model}: the turn must complete with the answer: finish={finish:?} text={text:?}"
);
assert!(
!thoughts.is_empty(),
"{model}: the turn was asked to think and showed no thoughts"
);
}
assert!(
client.thinks_within_a_budget(),
"{model} was declared to refuse adaptive thinking, yet nothing was refused — the recovery did not run"
);
}