newt-core 0.8.0

Newt-Agent core types, errors, and the NeMoCode-style tier router
use super::*;
use serde_json::json;

fn messages() -> Vec<Value> {
    vec![
        json!({"role":"system","content":"rules"}),
        json!({"role":"user","content":"old detail".repeat(150)}),
        json!({"role":"assistant","content":"prior answer"}),
        json!({"role":"user","content":"current task"}),
    ]
}
fn harness(enabled: bool) -> SmartHarness {
    SmartHarness::new(Session::new(crate::test_guard::unbudgeted_session_config()).unwrap(), Arc::new(|prompt| {
        let pages:Value=serde_json::from_str(prompt.lines().last().unwrap()).unwrap();
        let page=&pages[0];
        let cid=page["cards"].as_array().unwrap().iter().find(|c| c["preview"].as_str().unwrap().contains("old detail")).unwrap()["cid"].clone();
        let reply=json!({"catalog":page["catalog"],"expected_head":page["expected_head"],"changes":[{"cid":cid,"action":"park","reason":"older detail"}]}).to_string();
        Box::pin(async move {Ok((reply,None))})
    }),AdjudicationSettings{composition_enabled:enabled,..Default::default()}).unwrap()
}

#[test]
fn composition_off_by_default_and_not_advertised() {
    assert!(!AdjudicationSettings::default().composition_enabled);
    let h = harness(false);
    assert!(h.propose_context(&json!({})).is_err());
    assert!(!advertise(json!([]), Some(&h))
        .to_string()
        .contains("propose_context"));
    assert!(advertise(json!([]), Some(&harness(true)))
        .to_string()
        .contains("propose_context"));
}

#[test]
fn primary_composition_binds_all_four_provider_wires() {
    for wire in ["openai", "ollama", "anthropic", "responses"] {
        let h = harness(true);
        let m = messages();
        let field = if wire == "responses" {
            "input"
        } else {
            "messages"
        };
        let mut body = json!({"model":"fixture","messages":m});
        if field == "input" {
            body = json!({"model":"fixture","input":m});
        }
        if wire == "anthropic" {
            h.prepare_with_messages(&body, wire, &m).unwrap();
        } else {
            h.request(&body, wire).unwrap();
        }
        let page: Value = serde_json::from_str(&h.propose_context(&json!({})).unwrap()).unwrap();
        h.propose_context(&json!({"catalog":page["catalog"],"expected_head":page["expected_head"],"changes":[{"cid":page["cards"][1]["cid"],"action":"park","reason":"irrelevant older detail"}]})).unwrap();
        let bytes = if wire == "anthropic" {
            h.prepare_with_messages(&body, wire, &m).unwrap()
        } else {
            h.request(&body, wire).unwrap()
        };
        assert!(
            !String::from_utf8_lossy(&bytes).contains("old detail"),
            "{wire}"
        );
        assert_eq!(h.replay_last_request().unwrap(), bytes, "{wire}");
        let mut state = h.state().unwrap();
        let decision = state.session.composition_head().unwrap().unwrap();
        assert_eq!(
            state
                .session
                .composition_decision(decision)
                .unwrap()
                .outcome,
            agent_harness::composition::Outcome::Accepted
        );
        let bound = state.session.record_composed_request().unwrap();
        assert_eq!(bound.composition, Some(decision));
        assert_eq!(bound.bytes, bytes);
    }
}

#[tokio::test]
async fn auxiliary_uses_same_proposal_validator_then_binds_real_template() {
    let h = harness(true);
    let m = messages();
    let projected = h.project(&m, 400).await.unwrap();
    assert_eq!(projected.len(), 3);
    assert!(h.state().unwrap().session.has_pending_composition());
    let body = json!({"model":"fixture","messages":projected,"temperature":0});
    let sent = h.request(&body, "openai").unwrap();
    assert_eq!(serde_json::from_slice::<Value>(&sent).unwrap(), body);
    assert!(!h.state().unwrap().session.has_pending_composition());
    assert_eq!(h.replay_last_request().unwrap(), sent);
}

#[tokio::test]
async fn malformed_auxiliary_stops_without_legacy_fallback() {
    let h = SmartHarness::new(
        Session::new(crate::test_guard::unbudgeted_session_config()).unwrap(),
        Arc::new(|_| Box::pin(async { Ok(("[]".into(), None)) })),
        AdjudicationSettings {
            composition_enabled: true,
            ..Default::default()
        },
    )
    .unwrap();
    assert!(h.project(&messages(), 400).await.is_err());
    assert!(h
        .state()
        .unwrap()
        .session
        .composition_head()
        .unwrap()
        .is_none());
}

#[tokio::test(start_paused = true)]
async fn composition_auxiliary_cancellation_is_recorded_and_bounded() {
    let h = SmartHarness::new(
        Session::new(crate::test_guard::unbudgeted_session_config()).unwrap(),
        Arc::new(|_| Box::pin(std::future::pending())),
        AdjudicationSettings {
            composition_enabled: true,
            ..Default::default()
        },
    )
    .unwrap();
    let before = h.head().unwrap();
    let m = messages();
    tokio::select! {
        result=h.project(&m,400)=>panic!("composition completed before cancellation: {result:?}"),
        _=tokio::time::sleep(Duration::from_millis(10))=>{},
    }
    assert_ne!(h.head().unwrap(), before);
    assert!(!h.state().unwrap().session.has_pending_composition());
    assert!(h
        .state()
        .unwrap()
        .session
        .composition_head()
        .unwrap()
        .is_none());
    assert!(h.state().unwrap().auxiliary_elapsed >= Duration::from_millis(10));
}