magi-code 0.80.2

Repository-aware CLI coding agent for terminal work
Documentation
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()
    {
        // Recreate the agent and reopen durable history every turn.
        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")
        );
    }
}