use super::*;
use crate::providers::MessageRole;
use crate::thinking::ThinkingLevel;
fn reasoning_agent(provider: &str, model: &str, effort: ThinkingLevel) -> AgentSession {
AgentSession::new(model, &[], &SkillDiscovery::default())
.with_provider_id(provider)
.with_thinking_level(effort)
.with_reasoning_updates(true)
}
fn updates(request: &ProviderRequest) -> Vec<serde_json::Value> {
request
.conversation_items_iter()
.filter_map(|item| match item {
ProviderConversationItem::ResponseItem(item)
if item["type"] == "configuration_update" =>
{
Some(item.clone())
}
_ => None,
})
.collect()
}
#[test]
fn reasoning_updates_survive_resume_unchanged_turns_and_tool_continuation() {
let temp = tempfile::TempDir::new().unwrap();
let manager = crate::sessions::SessionManager::new(temp.path().join("sessions"));
let session = manager.create().unwrap();
let provider = ScriptedProvider::new(vec![
text_done("first answer"),
vec![
tool_call("read_file", "read", json!({"paths": ["sample.txt"]})),
done(),
],
text_done("second answer"),
text_done("third answer"),
text_done("fourth answer"),
]);
std::fs::write(temp.path().join("sample.txt"), "sample").unwrap();
let tools = ToolRuntime::new(temp.path()).unwrap();
for (index, effort) in [
ThinkingLevel::Low,
ThinkingLevel::High,
ThinkingLevel::High,
ThinkingLevel::Medium,
]
.into_iter()
.enumerate()
{
let reopened = manager.open(session.id()).unwrap();
reasoning_agent("openai-codex", "gpt-6-astra", effort)
.run_print_with_tools_streaming_output(
&provider,
&format!("question {index}"),
Some(&tools),
Some(&reopened),
temp.path(),
None,
)
.unwrap();
}
let requests = provider.requests();
assert_eq!(requests.len(), 5);
assert!(
requests
.iter()
.all(|request| request.thinking_level == ThinkingLevel::Low)
);
assert!(updates(&requests[0]).is_empty());
assert_eq!(
updates(&requests[1]),
vec![json!({"type":"configuration_update","reasoning":{"effort":"high"}})]
);
assert_eq!(updates(&requests[1]), updates(&requests[2]));
assert_eq!(updates(&requests[2]), updates(&requests[3]));
assert_eq!(
updates(&requests[4])
.iter()
.map(|item| item["reasoning"]["effort"].as_str().unwrap())
.collect::<Vec<_>>(),
["high", "medium"]
);
for request in &requests[1..] {
let items = request.conversation_items();
for pair in items.windows(2) {
if matches!(&pair[0], ProviderConversationItem::ResponseItem(item) if item["type"] == "configuration_update")
{
assert!(
matches!(&pair[1], ProviderConversationItem::Message(message) if message.role == MessageRole::User)
);
}
}
}
let inputs = session
.read_events()
.unwrap()
.into_iter()
.filter(|event| event.event_type == "user_input")
.collect::<Vec<_>>();
assert_eq!(inputs.len(), 4);
assert_eq!(inputs[0].payload["reasoning_selection"]["effort"], "low");
assert_eq!(inputs[3].payload["reasoning_selection"]["effort"], "medium");
}
#[test]
fn reasoning_updates_keep_provider_and_model_baselines_separate_and_reset_after_compaction() {
let temp = tempfile::TempDir::new().unwrap();
let session = crate::sessions::SessionManager::new(temp.path().join("sessions"))
.create()
.unwrap();
let provider = ScriptedProvider::new((0..5).map(|_| text_done("answer")).collect());
for (id, model, effort) in [
("openai-codex", "gpt-6-astra", ThinkingLevel::Low),
("official-openai", "gpt-6-astra", ThinkingLevel::Medium),
("openai-codex", "other-model", ThinkingLevel::Medium),
("openai-codex", "gpt-6-astra", ThinkingLevel::High),
] {
reasoning_agent(id, model, effort)
.run_print_with_tools_streaming_output(
&provider,
"question",
None,
Some(&session),
temp.path(),
None,
)
.unwrap();
}
let cutoff = session.read_events().unwrap().len();
crate::sessions::record_session_compaction(
&session,
temp.path(),
"summary",
"openai-codex",
"gpt-6-astra",
cutoff,
)
.unwrap();
reasoning_agent("openai-codex", "gpt-6-astra", ThinkingLevel::High)
.run_print_with_tools_streaming_output(
&provider,
"after compaction",
None,
Some(&session),
temp.path(),
None,
)
.unwrap();
let requests = provider.requests();
assert!(updates(&requests[1]).is_empty());
assert_eq!(requests[1].thinking_level, ThinkingLevel::Medium);
assert!(updates(&requests[2]).is_empty());
assert_eq!(requests[3].thinking_level, ThinkingLevel::Low);
assert_eq!(updates(&requests[3]).len(), 1);
assert_eq!(requests[4].thinking_level, ThinkingLevel::High);
assert!(updates(&requests[4]).is_empty());
assert!(
requests[4]
.messages()
.iter()
.any(|message| message.content.contains("summary"))
);
}
#[test]
fn reasoning_updates_default_and_opt_out_do_not_guess_an_effort() {
let temp = tempfile::TempDir::new().unwrap();
let session = crate::sessions::SessionManager::new(temp.path().join("sessions"))
.create()
.unwrap();
let provider = ScriptedProvider::new((0..4).map(|_| text_done("answer")).collect());
for effort in [
ThinkingLevel::Low,
ThinkingLevel::High,
ThinkingLevel::Default,
] {
reasoning_agent("openai-codex", "gpt-6-astra", effort)
.run_print_with_tools_streaming_output(
&provider,
"question",
None,
Some(&session),
temp.path(),
None,
)
.unwrap();
}
reasoning_agent("openai-codex", "gpt-6-astra", ThinkingLevel::Medium)
.with_reasoning_updates(false)
.run_print_with_tools_streaming_output(
&provider,
"ordinary request",
None,
Some(&session),
temp.path(),
None,
)
.unwrap();
let requests = provider.requests();
assert_eq!(requests[2].thinking_level, ThinkingLevel::Default);
assert!(updates(&requests[2]).is_empty());
assert_eq!(requests[3].thinking_level, ThinkingLevel::Medium);
assert!(updates(&requests[3]).is_empty());
}
#[test]
fn reasoning_updates_retain_baseline_from_partial_terminal_turns() {
for status in ["cancelled", "failed"] {
let temp = tempfile::TempDir::new().unwrap();
let session = crate::sessions::SessionManager::new(temp.path().join("sessions"))
.create()
.unwrap();
for (kind, payload) in [
(
SessionEventKind::UserInput,
json!({"text":"earlier question", "reasoning_selection": {
"provider":"openai-codex", "model":"gpt-6-astra", "effort":"low"
}}),
),
(
SessionEventKind::TurnStatus,
json!({"status":status,"assistant_text":"partial answer"}),
),
] {
crate::sessions::record_session_event(Some(&session), temp.path(), kind, payload)
.unwrap();
}
let provider = ScriptedProvider::new(vec![text_done("finished")]);
reasoning_agent("openai-codex", "gpt-6-astra", ThinkingLevel::High)
.run_print_with_tools_streaming_output(
&provider,
"next question",
None,
Some(&session),
temp.path(),
None,
)
.unwrap();
let requests = provider.requests();
assert_eq!(requests[0].thinking_level, ThinkingLevel::Low);
assert_eq!(updates(&requests[0])[0]["reasoning"]["effort"], "high");
assert!(
requests[0]
.messages()
.iter()
.any(|message| message.content == "partial answer")
);
}
}